Почему этот действительный код Tkinter падает при смешивании с небольшим количеством PyWin32? - PullRequest
1 голос
/ 29 декабря 2010

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

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

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

1 Ответ

2 голосов
/ 06 января 2011

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