Почему этот допустимый код Tkinter дает сбой при смешивании с небольшим количеством PyWin32?

Итак, я делаю очень маленькую программу для личного пользования в tkinter и наткнулся на очень странную стену. Я смешиваю tkinter с привязками pywin32, потому что я действительно ненавижу все, что связано с синтаксисом и соглашениями об именах pywin32, и мне кажется, что tkinter делает больше с гораздо меньшим количеством кода. Странность происходит при переходе между просмотром буфера обмена pywin32 и реакцией моей программы на него в tkinter.

Мое окно и все его элементы управления обрабатываются в tkinter. Привязки pywin32 выполняют просмотр буфера обмена и доступ к буферу обмена при изменении буфера обмена. Из того, что я узнал о том, как буфер обмена просматривает части pywin32, вы можете заставить его работать с чем угодно, если вы предоставите pywin32 значение hwnd вашего окна. Я делаю эту часть, и она работает при первом запуске программы. Это просто не работает при изменении буфера обмена.

Когда программа запускается, она захватывает буфер обмена и помещает его в поле поиска и поле редактирования. Когда буфер обмена изменен, событие, которое я хочу запустить, срабатывает ... за исключением того события, которое полностью работало раньше, когда программа запускалась, теперь вызывает странное зависание вместо того, чтобы делать то, что он должен делать. Я могу распечатать содержимое буфера обмена в stdout все, что захочу, если буфер обмена изменится, но не поместить те же данные в виджет tkinter. Он зависает только в том случае, если он начинает взаимодействовать с любым из моих виджетов tkinter после того, как он был запущен уведомлением об изменении буфера обмена.

Такое ощущение, что есть какой-то этикет pywin32, который я пропустил при адаптации примера кода для просмотра буфера обмена, который я использовал, к моей программе, использующей tkinter. 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()

person Erlog    schedule 29.12.2010    source источник


Ответы (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)
person schlenk    schedule 06.01.2011
comment
Я думаю, что это, вероятно, сработает, но мне придется опросить какой-то основной цикл, чтобы вызвать обновление с помощью логической переменной, размещенной в другом месте. Я ценю вашу попытку ответить на этот вопрос. После столь долгого отсутствия ответа я просто перевел весь проект на wxPython. Мне не очень нравится wxPython, но для этого небольшого проекта гораздо проще просто его полностью внедрить. - person Erlog; 12.01.2011