Фабрика для методов обратного вызова - Python TKinter

1

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

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

#!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()

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

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

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

Теги:
methods
factory

3 ответа

1
Лучший ответ

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

  • 0
    Да, это было это! «Я» было излишним в методе «cb», возвращаемом «cb_factory». Я опубликую исправленный код, чтобы показать рабочий пример. Теперь, если кто-то любезно объяснит мне это, у меня болит мозг!
1

В ответ на ваш последующий вопрос.

Я не уверен, какую часть вы не понимаете, но я предполагаю, что вы не совсем справляетесь с тем, как работают обратные вызовы событий? Если так, то это довольно легко. Tk запускается в цикле поиска событий (нажатия клавиш, mouseclicks и т.д.). Когда вы связываете обратный вызов/функцию с событием, вы просто говорите ему, чтобы он вызывал вашу функцию и передавал объект события в качестве аргумента. Затем вы можете запросить объект события для получения более подробной информации о том, что произошло на самом деле. В настоящее время вы создаете отдельные функции обратного вызова и привязываете их к 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 как обратный вызов, он должен получить себя как свой первый аргумент. Он получает свой ключевой код от объекта события. Теперь ни одна ценность не должна быть "встроена" в функцию при создании функции, поэтому необходимость создания разных обратных вызовов для каждой клавиши будет удалена, а код легче понять.

  • 0
    Это ключ = event.char Но меня больше интересовало, почему производимому фабрикой методу cb не нужен 'self' в качестве первого параметра.
  • 1
    потому что он унаследовал значение self от фабричной функции, почти как глобальная переменная. self не определено в cb, но определено в области видимости выше (метод объекта)
0

Вот исправленный код с учетом ответа 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()

Ещё вопросы

Сообщество Overcoder
Наверх
Меню