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

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.

   1 import wx
   2 import random
   3 import time
   4 import win32clipboard

Register the RTF format with the clipboard.

   1 def __init__(self):
   2     self.CF_RTF = win32clipboard.RegisterClipboardFormat("Rich Text Format")

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

Comments

Display RTF Using the Clipboard (last edited 2008-03-11 10:50:24 by localhost)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.