Introduction
There may be times in life when you would like to display Rtf text in a text control. You already have the Rtf code; you just need a way to get it to appear as formatted text in the text box. Until wxPython gets it's own Rtf display control, there's no great way of doing this aside from creating your own Rtf text object. The method of displaying the formatted text described here involves using the Windows clipboard to do all the work for you.
Note that this solution is platform specific: WINDOWS ONLY.
Objects Involved
(wx)win32clipboard
(wx)random
(wx)time
Process Overview
Basically, this code registers/uses the "Rich Text Format" clipboard format and writes the rich text into the clipboard, allowing you to paste it into a text control as RTF. Because it's being saved in the RTF part of the clipboard, Windows will do all the display work upon paste.
The built-in wxClipboard would only allow us to paste into the RTF clipboard format if we created our own RTF text object. Here, we will talk with the clipboard a little more directly to get things done.
Special Concerns
This approach is rather hackish for showing Rtf and probably has some issues. The main concern is that the user's existing clipboard data will be erased through this process. Included, though, are methods thats save the user's existing clipboard initially and restore them in the end. This clipboard backup doesn't seem to work copied files, image data, etc...only text-based things (HTML,RTF,Unicode,text).
**Also, justification information isn't pasted.
Code to Write RTF to the Clipboard
Do the imports.
Register the RTF format with the clipboard.
Put stuff on the board.
1 # Puts 'toPaste' on the clipboard
2 def setClipboard(self,toPaste):
3 cbOpened = False
4 # Wait for board availability, then do operations
5 while not cbOpened:
6 try:
7 win32clipboard.OpenClipboard(0)
8 cbOpened = True
9 win32clipboard.EmptyClipboard() # need to empty, or prev data will stay
10 win32clipboard.SetClipboardData(self.CF_RTF, toPaste)
11 win32clipboard.CloseClipboard()
12 except Exception, err:
13 # If access is denied, that means that the clipboard is in use.
14 # Keep trying until it's available.
15 if err[0] == 5: #Access Denied
16 pass
17 #print 'waiting on clipboard...'
18 # wait on clipboard because something else has it. we're waiting a
19 # random amount of time before we try again so we don't collide again
20 time.sleep( random.random()/50 )
21 elif err[0] == 1418: #doesn't have board open
22 pass
23 elif err[0] == 0: #open failure
24 pass
25 else:
26 print 'ERROR in Clipboard section of readcomments: %s' % err
27 pass
What's happening is that it tries to open the clipboard, but it may be in use, so we keep trying until it's available. The board is then emptied, data is written, and the board closed.
When board is inaccessible, we wait a small random amount of time before trying again so that if the program that was originally holding onto the board and not allowing us access tries to connect back, there will less of a chance of another 'collision'. (Think networking) This is mainly necessary if you have two programs running simultaneously that are trying to access the clipboard repeatedly and very frequently.
Code to Save Clipboard
This saves the user's data. The CF_HDROP format (#15) can't just be straight copied, so we give up and move on.
1 # Save the user's existing clipboard data, if possible. It is unable to save
2 # copied files, image data, etc; text, HTML, RTF, etc are preserved just fine
3 def saveClipboard(self):
4 cbOpened = False
5 while not cbOpened:
6 try:
7 win32clipboard.OpenClipboard(0)
8 cbOpened = True
9
10 self.cbSaved = {}
11 rval = win32clipboard.EnumClipboardFormats( 0 )
12 while rval != 0:
13 #print "Retrieving CB format %d" % rval
14 dat = win32clipboard.GetClipboardData( rval )
15 if rval == 15: #CF_HDROP
16 #this'll error, so just give up
17 self.cbSaved = {}
18 win32clipboard.EmptyClipboard()
19 break
20 else:
21 self.cbSaved[ rval ] = win32clipboard.GetClipboardData( rval )
22 rval = win32clipboard.EnumClipboardFormats( rval )
23 win32clipboard.CloseClipboard()
24 except Exception, err:
25 if err[0] == 5: #Access Denied
26 #print 'waiting on clipboard...'
27 time.sleep( random.random()/50 )
28 pass
29 elif err[0]== 6:
30 #print 'clipboard type error, aborting...'
31 win32clipboard.CloseClipboard()
32 break
33 elif err[0] == 1418: #doesn't have board open
34 cbOpened = False
35 elif err[0] == 0: #open failure
36 cbOpened = False
37 else:
38 print 'Error while saving clipboard: %s' % err
39 pass
Code to Restore Clipboard
1 # Restore the user's clipboard, if possible
2 def restoreClipboard(self):
3 cbOpened = False
4
5 # don't wait for the CB if we don't have to
6 if len(self.cbSaved) > 0:
7 #open clipboard
8 while not cbOpened:
9 try:
10 win32clipboard.OpenClipboard(0)
11 win32clipboard.EmptyClipboard()
12 cbOpened = True
13 except Exception, err:
14 if err[0] == 5: #Access Denied
15 #print 'waiting on clipboard...'
16 time.sleep( random.random()/50 )
17 pass
18 elif err[0] == 1418: #doesn't have board open
19 cbOpened = False
20 elif err[0] == 0: #open failure
21 cbOpened = False
22 else:
23 print 'Error with clipboard restoration: %s' % err
24 pass
25 #replace items
26 try:
27 for item in self.cbSaved:
28 data = self.cbSaved.get(item)
29 # windows appends NULL to most clipboard items, so strip off the NULL
30 if data[-1] == '\0':
31 data = data[:-1]
32 win32clipboard.SetClipboardData( item, data )
33 except Exception, err:
34 #print 'ERR: %s' % err
35 win32clipboard.EmptyClipboard()
36 try:
37 win32clipboard.CloseClipboard()
38 except:
39 pass
Test Program
I put these methods into a module called 'rtfClip'. Here is a test program that shows the functionality of this process.
1 import wx
2 import rtfClip
3
4 class App(wx.PySimpleApp):
5 def OnInit(self):
6 frame = wx.Frame(parent=None,size=(640,480),title='Test RTF')
7 tbox = wx.TextCtrl(frame,-1,size=(100,100),
8 style=wx.TE_MULTILINE | wx.TE_RICH2 | wx.EXPAND)
9 frame.Show()
10
11 sampleData = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033" + \
12 "{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}" + \
13 "{\\f1\\fmodern\\fprq1\\fcharset0 Courier New;}" + \
14 "{\\f2\\fdecor\\fprq2\\fcharset0 Broadway;}}\n" + \
15 "{\\colortbl ;\\red0\\green0\\blue255;\\red128\\green0\\blue128;" + \
16 "\\red255\\green0\\blue255;}\n" + \
17 "\\viewkind4\\uc1\\pard\\f0\\fs24 This sample \\b TEXT \\b0 shows how" + \
18 "\\ul\\i RTF \\ulnone\\i0 data\\fs20\\par\n" + \
19 "\\f1\\fs22 can be displayed in a \\cf1 wxTextCtrl \\cf0 text box" + \
20 "by first copying the data\\f0\\fs20\\par\n" + \
21 "\\f2\\fs32 to the \\cf2 clipboard\\cf0 , then \\cf3 pasting \\cf0 it" + \
22 "into the box.\\f0\\fs20\\par\n}"
23
24 rtf = rtfClip.rtfClip()
25 rtf.saveClipboard() #Save the current user's clipboard
26 rtf.setClipboard(sampleData) #Put our RTF on the clipboard
27 tbox.Paste() #Paste in into the textbox
28 rtf.restoreClipboard() #Restore the user's clipboard
29 return True
30
31 if __name__ == '__main__':
32 app = App()
33 app.MainLoop()
So that's that. Please let me know if you have a better way of displaying RTF as well as backing up/restoring the clipboard--one that will work in all cases. --JonathanRice