Фабрика для методов обратного вызова - Python TKinter - PullRequest
1 голос
/ 26 мая 2009

При написании тестового приложения для эмуляции линий PIO у меня есть очень простое приложение с графическим интерфейсом Python / Tk. Используя цифровые клавиши с 1 по 8 для имитации контактов PIO с 1 по 8. Нажмите клавишу = PIO High, отпустите клавишу = PIO понизится. То, что мне нужно, это не проблема. Я как бы спустился в кроличью нору, пытаясь использовать фабрику для создания функций обратного вызова при нажатии клавиш.

Вот некоторый урезанный код:

#!usr/bin/env python
"""
Python + Tk GUI interface to simulate a 8 Pio lines.
"""

from Tkinter import *

def cb_factory(numberic_key):
    """
    Return a call back function for a specific keyboard numeric key (0-9)
    """
    def cb( self, event, key=numberic_key ):
        bit_val = 1<<numberic_key-1
        if int(event.type) == 2 and not (bit_val & self.bitfield):
            self.bitfield |= bit_val
            self.message("Key %d Down" % key)
        elif int(event.type) == 3 and (bit_val & self.bitfield):
            self.bitfield &= (~bit_val & 0xFF)
            self.message("Key %d Up" % key)
        else:
            # Key repeat
            return
        print hex(self.bitfield)
        self.display_bitfield()
    return cb

class App( Frame ):
    """
    Main TK App class 
    """

    cb1 = cb_factory(1)
    cb2 = cb_factory(2)
    cb3 = cb_factory(3)
    cb4 = cb_factory(4)
    cb5 = cb_factory(5)
    cb6 = cb_factory(6)
    cb7 = cb_factory(7)
    cb8 = cb_factory(8)

    def __init__(self, parent):
        "Init"
        self.parent = parent
        self.bitfield = 0x00
        Frame.__init__(self, parent)

        self.messages = StringVar()
        self.messages.set("Initialised")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.messages,
               text="Testing" ).pack(fill=X)

        self.bf_label = StringVar()
        self.bf_label.set("0 0 0 0 0 0 0 0")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.bf_label,
               text="Testing" ).pack(fill=X)

 # This Doesn't work! Get a traceback saying 'cb' expected 2 arguements
 # but only got 1?
 #
 #       for x in xrange(1,9):
 #           cb = self.cb_factory(x)
 #           self.parent.bind("<KeyPress-%d>" % x, cb) 
 #           self.parent.bind("<KeyRelease-%d>" % x, cb) 

        self.parent.bind("<KeyPress-1>", self.cb1)
        self.parent.bind("<KeyRelease-1>", self.cb1)

        self.parent.bind("<KeyPress-2>", self.cb2)
        self.parent.bind("<KeyRelease-2>", self.cb2)

        self.parent.bind("<KeyPress-3>", self.cb3)
        self.parent.bind("<KeyRelease-3>", self.cb3)

        self.parent.bind("<KeyPress-4>", self.cb4)
        self.parent.bind("<KeyRelease-4>", self.cb4)

        self.parent.bind("<KeyPress-5>", self.cb5)
        self.parent.bind("<KeyRelease-5>", self.cb5)

        self.parent.bind("<KeyPress-6>", self.cb6)
        self.parent.bind("<KeyRelease-6>", self.cb6)

        self.parent.bind("<KeyPress-7>", self.cb7)
        self.parent.bind("<KeyRelease-7>", self.cb7)

        self.parent.bind("<KeyPress-8>", self.cb8)
        self.parent.bind("<KeyRelease-8>", self.cb8)


    def display_bitfield(self):
        """
        Display the PIO lines (1 for on, 0 for off)
        """
        bin_lst = []
        for x in xrange(8):
            bit = 1 << x
            if bit & self.bitfield:
                bin_lst.append("1")
            else:
                bin_lst.append("0")
        bin_lst.reverse()
        bin_str = " ".join( bin_lst )
        self.bf_label.set( bin_str )

    def message( self, msg_txt ):
        "set"
        self.messages.set( msg_txt )

    def cb_factory(self,  numberic_key ):
        """
        Return a call back function for a specific keyboard numeric key (0-9)
        """
        def cb( self, event, key=numberic_key ):
            bit_val = 1<<numberic_key-1
            if int(event.type) == 2:
                self.bitfield |= bit_val
                self.message("Key %d Down" % key)
            else:
                self.bitfield &= (~bit_val & 0xFF)
                self.message("Key %d Up" % key)
            print hex(self.bitfield)
            self.display_bitfield()
        return cb

##########################################################################

if __name__ == "__main__":

    root = Tk()
    root.title("PIO Test")
    theApp = App( root )

    root.mainloop()

Я наконец-то получил какую-то фабрику методов, работающую для обратного вызова, но я не нахожу ее очень удовлетворительной.

Итак, мой вопрос, можете ли вы иметь фабрику методов класса, которая будет производить методы класса так, как я пытался (см. Закомментированный код и метод класса App cb_factory ())?

ПРИМЕЧАНИЯ. Да, я знаю, что это приложение позволяет удерживать только 4 клавиши одновременно, но этого вполне достаточно для моих целей.

Ответы [ 3 ]

1 голос
/ 26 мая 2009

В ответ на ваш дополнительный вопрос.

Я не уверен, какую часть ты не понимаешь, но, полагаю, у тебя не совсем понятно, как работают обратные вызовы событий? Если так, то это довольно легко. Tk запускается в цикле поиска событий (нажатий клавиш, щелчков мышью и т. Д.). Когда вы связываете обратный вызов / функцию с событием, вы просто указываете ей вызвать вашу функцию и передать объект события в качестве аргумента. Затем вы можете запросить объект события для получения более подробной информации о том, что на самом деле произошло. В настоящее время вы создаете отдельные функции обратного вызова и привязываете каждое к 18 событиям клавиш (вниз и отпустите клавиши 1-9). Я думаю, что вы можете переписать это, чтобы просто использовать cb в качестве метода вашего класса, потому что объект события почти наверняка будет также содержать код ключа.

class:
  def __init__(self):
    for x in xrange(8):
      self.parent.bind("<KeyPress-%d>" % x, self.keyaction)
      self.parent.bind("<KeyRelease-%d>" % x, self.keyaction)

  def keyaction(self, event):
    key = event.keycode # attribute may have another name, I haven't checked tk docs
    ... do stuff ...

Поскольку теперь мы используем self .keyaction в качестве обратного вызова, он должен получить self в качестве первого аргумента. Он получает свой код ключа от объекта события. Теперь ни одно из значений не нужно «встраивать» в функцию при ее создании, поэтому необходимость фактически создавать различные обратные вызовы для каждого ключа устраняется, и код легче понять.

1 голос
/ 26 мая 2009

cb ожидает «себя» и «событие». Может быть, это только получает событие от привязки?

0 голосов
/ 26 мая 2009

Вот исправленный код с учетом ответа SpliFF. Я нахожу это намного более эстетичным, но меня беспокоит, что я не понимаю, как это работает Итак, для дополнительного кредита, кто-нибудь может объяснить, как это работает?

#!usr/bin/env python
"""
Python + Tk GUI interface to simulate a 8 Pio lines.
"""

from Tkinter import *
from pio_handler import *

class App( Frame ):
    """
    Main TK App class 
    """

    def __init__(self, parent):
        "Init"
        self.parent = parent
        self.bitfield = 0x00
        Frame.__init__(self, parent)

        self.messages = StringVar()
        self.messages.set("Initialised")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.messages,
               text="Testing" ).pack(fill=X)

        self.bf_label = StringVar()
        self.bf_label.set("0 0 0 0 0 0 0 0")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.bf_label,
               text="Testing" ).pack(fill=X)

        # This is the clever bit!
        # Use a factory to assign a callback function for keys 1 to 8
        for x in xrange(1,9):
            cb = self.cb_factory(x)
            self.parent.bind("<KeyPress-%d>" % x, cb) 
            self.parent.bind("<KeyRelease-%d>" % x, cb) 

    def display_bitfield(self):
        """
        Display the PIO lines (1 for on, 0 for off)
        """
        bin_lst = []
        for x in xrange(8):
            bit = 1 << x
            if bit & self.bitfield:
                bin_lst.append("1")
            else:
                bin_lst.append("0")
        bin_lst.reverse()
        bin_str = " ".join( bin_lst )
        self.bf_label.set( bin_str )

    def message( self, msg_txt ):
        "set"
        self.messages.set( msg_txt )

    def cb_factory(self,  numeric_key ):
        """
        Return a call back function for a specific keyboard numeric key (0-9)
        """
        def cb( event, key=numeric_key ):
            bit_val = 1<<numeric_key-1
            if int(event.type) == 2:
                self.bitfield |= bit_val
                self.message("Key %d Down" % key)
            else:
                self.bitfield &= (~bit_val & 0xFF)
                self.message("Key %d Up" % key)
            print hex(self.bitfield)
            self.display_bitfield()
        return cb

##########################################################################

if __name__ == "__main__":

    root = Tk()
    root.title("PIO Test")
    theApp = App( root )

    root.mainloop()
...