Welcome, guest | Sign In | My Account | Store | Cart

With the "SetClipboardViewer" function you can add a window to the chain of clipboard viewers. Whenever the content of the clipboard changes, a message is send to the clipboard viewer windows. A clipboard viewer window must process two messages and pass them to the next window in the chain. With a sample about hooking the window procedure of a wxPython Frame, I made up the following clipboard viewer.

Python, 75 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import wx
import win32api
import win32gui
import win32con
import win32clipboard

class TestFrame (wx.Frame):
    def __init__ (self):
        wx.Frame.__init__ (self, None, title="Clipboard viewer", size=(250,150))

        self.first   = True
        self.nextWnd = None

        # Get native window handle of this wxWidget Frame.
        self.hwnd    = self.GetHandle ()

        # Set the WndProc to our function.
        self.oldWndProc = win32gui.SetWindowLong (self.hwnd,
                                                  win32con.GWL_WNDPROC,
                                                  self.MyWndProc)

        try:
            self.nextWnd = win32clipboard.SetClipboardViewer (self.hwnd)
        except win32api.error:
            if win32api.GetLastError () == 0:
                # information that there is no other window in chain
                pass
            else:
                raise

    def MyWndProc (self, hWnd, msg, wParam, lParam):
        if msg == win32con.WM_CHANGECBCHAIN:
            self.OnChangeCBChain (msg, wParam, lParam)
        elif msg == win32con.WM_DRAWCLIPBOARD:
            self.OnDrawClipboard (msg, wParam, lParam)

        # Restore the old WndProc. Notice the use of win32api
        # instead of win32gui here. This is to avoid an error due to
        # not passing a callable object.
        if msg == win32con.WM_DESTROY:
            if self.nextWnd:
               win32clipboard.ChangeClipboardChain (self.hwnd, self.nextWnd)
            else:
               win32clipboard.ChangeClipboardChain (self.hwnd, 0)

            win32api.SetWindowLong (self.hwnd,
                                    win32con.GWL_WNDPROC,
                                    self.oldWndProc)

        # Pass all messages (in this case, yours may be different) on
        # to the original WndProc
        return win32gui.CallWindowProc (self.oldWndProc,
                                        hWnd, msg, wParam, lParam)

    def OnChangeCBChain (self, msg, wParam, lParam):
        if self.nextWnd == wParam:
           # repair the chain
           self.nextWnd = lParam
        if self.nextWnd:
           # pass the message to the next window in chain
           win32api.SendMessage (self.nextWnd, msg, wParam, lParam)

    def OnDrawClipboard (self, msg, wParam, lParam):
        if self.first:
           self.first = False
        else:
           print "clipboard content changed"
        if self.nextWnd:
           # pass the message to the next window in chain
           win32api.SendMessage (self.nextWnd, msg, wParam, lParam)

app   = wx.PySimpleApp ()
frame = TestFrame ()
frame.Show ()
app.MainLoop ()

Warning When you abort this script with Ctrl-C etc. the frame not receives the WM_DESTROY message and the clipboard chain could be left damaged.

Task You get informed whenever the clipboard content changes without the need to poll . I use this in an wxPython/Pyro application to sync the clipboard of computers over the network.

Alternatives I think you can not do this with wx.TheClipboard () because it is a platform dependent feature. You can do this without wxPython using functions like "win32gui.CreateWindow". There is already a 95 % working example on the net. Google for "win32clipboard.SetClipboardViewer"! Unfortunately, I don't know if there is a way to do this on linux.

1 comment

Vincent Wehren 17 years, 9 months ago  # | flag

Add a view to the viewer. Why not add a little view to the viewer? ;)

In __init__ add:

self.tc = wx.TextCtrl(self, -1
                          , style=wx.TE_MULTILINE
                                  |wx.TE_READONLY)

Now add the method:

def GetTextFromClipboard(self):
    """
    """
    clipboard = wx.Clipboard()
    if clipboard.Open():
        if clipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
            data = wx.TextDataObject()
            clipboard.GetData(data)
            s = data.GetText()
            self.tc.AppendText("Clip content:\n%s\n\n" % s )
            clipboard.Close()
        else:
            self.tc.AppendText("")

Subsequently, change your OnDrawClipboard method to something like:

def OnDrawClipboard (self, msg, wParam, lParam):
    if self.first:
       self.first = False
    else:
       self.tc.AppendText("[Clipboard content changed:]\n")
       self.GetTextFromClipboard()
    if self.nextWnd:
       # pass the message to the next window in chain
       win32api.SendMessage (self.nextWnd, msg, wParam, lParam)