【问题标题】:Why does this valid Tkinter code crash when mixed with a bit of PyWin32?为什么这个有效的 Tkinter 代码与一些 PyWin32 混合时会崩溃?
【发布时间】:2010-12-29 15:51:34
【问题描述】:

所以我在 tkinter 中制作了一个非常小的程序供个人使用,我遇到了一个非常奇怪的墙。我将 tkinter 与 pywin32 绑定混合在一起,因为我真的很讨厌与 pywin32 的语法和命名约定有关的一切,而且感觉 tkinter 用更少的代码完成了更多工作。奇怪的是在 pywin32 剪贴板观看和我的程序在 tkinter 中对它的反应之间的过渡。

我的窗口及其所有控件都在 tkinter 中处理。当剪贴板更改时,pywin32 绑定正在执行剪贴板监视和剪贴板访问。根据我收集到的有关剪贴板查看 pywin32 工作方式的信息,只要您为 pywin32 提供窗口的 hwnd 值,您就可以使其与任何您想要的东西一起工作。我正在做那部分,它在程序第一次启动时起作用。当剪贴板更改时,它似乎不起作用。

当程序启动时,它会抓取剪贴板并将其放入搜索框和编辑框就可以了。当剪贴板被修改时,我想要触发的事件正在触发......除了在程序启动之前完全有效的事件现在导致奇怪的挂起而不是做它应该做的事情。如果剪贴板发生更改,我可以将剪贴板内容打印到标准输出,但不能将相同的数据放入 tkinter 小部件中。只有当它在被剪贴板更改通知触发后开始与我的任何 tkinter 小部件交互时,它才会这样挂起。

感觉在将我使用的剪贴板观看示例代码改编为我的 tkinter 使用程序时,我错过了一些 pywin32 礼仪。 Tkinter 显然不喜欢产生堆栈跟踪或错误消息,我什至不知道要寻找什么来尝试使用 pdb 对其进行调试。

代码如下:

#coding: utf-8
#Clipboard watching cribbed from ## {{{ http://code.activestate.com/recipes/355593/ (r1)

import pdb
from Tkinter import *
import win32clipboard
import win32api
import win32gui
import win32con
import win32clipboard


def force_unicode(object, encoding="utf-8"):
    if isinstance(object, basestring) and not isinstance(object, unicode):
        object = unicode(object, encoding)
    return object

class Application(Frame):
    def __init__(self, master=None):
        self.master = master
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

        self.hwnd = self.winfo_id()
        self.nextWnd = None
        self.first = True
        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

        self.update_search_box()
        self.word_search()

    def word_search(self):
        #pdb.set_trace()
        term = self.searchbox.get()
        self.resultsbox.insert(END, term)

    def update_search_box(self):
        clipboardtext = ""
        if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_TEXT):
            win32clipboard.OpenClipboard()
            clipboardtext = win32clipboard.GetClipboardData()
            win32clipboard.CloseClipboard()

        if clipboardtext != "":
            self.searchbox.delete(0,END)
            clipboardtext = force_unicode(clipboardtext)
            self.searchbox.insert(0, clipboardtext)

    def createWidgets(self):
        self.button = Button(self)
        self.button["text"] = "Search"
        self.button["command"] = self.word_search

        self.searchbox = Entry(self)
        self.resultsbox = Text(self)

        #Pack everything down here for "easy" layout changes later
        self.searchbox.pack()
        self.button.pack()
        self.resultsbox.pack()

    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 "changed"
            self.word_search()
            #self.word_search()

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


if __name__ == "__main__":
    root = Tk()
    app = Application(master=root)
    app.mainloop()
    root.destroy()

【问题讨论】:

    标签: python crash tkinter clipboard pywin32


    【解决方案1】:

    不确定它是否有帮助,但我认为它会在您从 win32 事件处理程序内部调用更新时崩溃,而 tkinter 可能不喜欢这样。

    解决此问题的常用技巧是通过 after_idle() 回调延迟更新。

    所以尝试替换:

       def OnDrawClipboard (self, msg, wParam, lParam):
        if self.first:
           self.first = False
        else:
            #print "changed"
            self.word_search()
            #self.word_search()
    
        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 "changed"
            self.after_idle(self.word_search)
            #self.word_search()
    
        if self.nextWnd:
           # pass the message to the next window in chain
           win32api.SendMessage(self.nextWnd, msg, wParam, lParam)
    

    【讨论】:

    • 我认为这可能会起作用,但我必须在某种主循环中轮询以使用放置在其他地方的布尔变量来触发更新。感谢您尝试回答这个问题。经过这么长时间没有回应,我只是将整个项目转换为 wxPython。我不是很喜欢 wxPython,但是对于这个小项目来说,完全采用它就不会那么令人头疼了。
    猜你喜欢
    • 2011-01-06
    • 1970-01-01
    • 2018-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-24
    • 2023-03-07
    • 2010-11-23
    相关资源
    最近更新 更多