Уведомления

Группа в Telegram: @pythonsu

#1 Май 14, 2013 14:53:31

moron
Зарегистрирован: 2012-10-17
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

tkinter,потоки

Прошу помощи: я не знаю, как повесить событие на несколько одновременно нажатых клавиш. window.bind('<Клавиша1><Клавиша2>', Функция) не подходит, поскольку срабатывает только при нажатий клавиш порядке и один раз, даже если прицепить модификатор KeyPress. Можно поставить на сочетание с такими клавишами как shift, ctrl. alt, end …(их много) , но мне необходимы две стрелочки.
Уже есть бинды на все стрелочки(1клавиша - 1вызов). Я думал, что нажму две стрелочки и будет меня 2 вызова ф-ий, но нет: при нажатие двух клавиш срабатывает лишь событие одной, а соответственно один обработчик. У меня было 2 мысли, почему такое поведение:
1)однопоточность приложения в целом
2)невозможность осуществлять запись, одновременно с разных потоков в один участок памяти.

Поэтому я разбил каждый бинд(всего 4 бинда, все стрелки) в отдельный поток и сделал очередь. Получилось модель(или как это называется?) производитель\потребитель. Но одновременно два события работать серавно не могли, что в целом предположительно. Ведь bind не вешает весь процесс, а работает как бэ уже работает в фоне, а значит в другом потоке?(я вложил поток в поток?)

Помогите решить мою задачу, надо при событии на двух кнопках одновременно вызывать их обработчики или создать еще одно условие, где необходимо будет нажимать две кнопки одновременно.

Можно вешать в обработчик кнопки еще один бинд и получится некая матрёшка, но это же не верный подход.
Можно проверять клавиши самостоятельно с помощью винапи, но это ведь тоже не правильный вариант.
И очень важно есть ли в моих раздумьях хоть зерно здравого смысла или это все фантазированный бред? Если найдется альтруистически настроенный форумчанин просьба объяснить мои логические ошибки и маразмы.
-
Очевидно, что у меня мало знаний. Может мне что полистать? лутц(которого я полностью не осилил, а лишь заглядываю в некоторые главы) и бизли есть.



Стучу по клаве как пианист по фортепьяно.

Отредактировано moron (Май 14, 2013 15:02:18)

Офлайн

#2 Май 14, 2013 20:02:17

4kpt
От: Харьков
Зарегистрирован: 2010-11-03
Сообщения: 998
Репутация: +  63  -
Профиль   Отправить e-mail  

tkinter,потоки

Пример кода. Тогда поговорим… Вы задали сразу несколько вопросов. Часть из них противоречит друг другу. Когда должно происходить действие? Когда нажаты эти две кнопки или когда отпущены? Короче. Код, дружище Битнер, код…



Офлайн

#3 Май 14, 2013 21:11:29

moron
Зарегистрирован: 2012-10-17
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

tkinter,потоки

Хорошо, сейчас скину, только такого ужаса вы наверное нигде не видели(индусы отдыхают). Мне стыдно, я не хотел такое вываливать.

#класс окна
class GameWin(t.Tk):  
    def __init__(self):
        print('in __init__')
        t.Tk.__init__(self)
        self.title('tank')
        self.geometry('800x800+150+150')
        self.resizable(False, False)
        self.canv = t.Canvas(self, width=800, height=800, bg='white')
        self.canv.pack()
        
    def addTexture(self, fileName):
        self.texture = ImageTk.PhotoImage(file=fileName)
        #Посколько минимальное кол-во текстуры 1, то +1
        XTexture = int(self.canv.cget('height'))//self.texture.height() +1
        YTexture = int(self.canv.cget('width'))//self.texture.width() + 1
        for y in range(0, YTexture):
            for x in range(0, XTexture):
                self.canv.create_image(x*int(self.texture.width()), y*int(self.texture.width()),
                                       image=self.texture, anchor='nw')
            
    def addImg(self, fileName):
        self.imgTank = ImageTk.PhotoImage(file=fileName)
        self.canv.create_image(50, 50, image=self.imgTank, tag='PLAYER')
 
win = GameWin()
win.addTexture('grass_4.jpg')
###########################
######Тут уйма вариантов#######
#######################
#1вариант. При нажатии двух клавиш лишь одно событие
#функции движения
def moveDown(event):
    x, y = win.canv.coords('PLAYER')
    win.canv.move('PLAYER', 0, 3)
def moveRight(event):
    x, y = win.canv.coords('PLAYER')
    win.canv.move('PLAYER', 3, 0)
#бинды
win.bind('<Down>', moveDown)
win.bind('<Right>', moveRight)
#########################
#2вариант. При нажатии двух клавиш одно событие. Потоки+очередь
#функции движения
def threadDown(event):
    q.put([0, 3])
def threadRight(event):
    q.put([3, 0])
    
def moveTank():
    coords = q.get()
    win.canv.move('PLAYER', coords[0], coords[1])
    moveTank()#рекурсия-потенциальная утечка памяти?
q = Queue()
#cами бинды.
threading.Thread(target=win.bind, args=('<Down>', threadDown)).start()
threading.Thread(target=win.bind, args=('<Right>', threadRight)).start()
threading.Thread(target=moveTank).start()
Когда должно происходить действие? Когда нажаты эти две кнопки или когда отпущены?
Когда нажата(зажата) хотя бы одна, в случае, если нажаты обе, то одновременно два евента-два обработчика(или же, если это возможно дополнительный бинд на две сразу на две кнопки(bind('<Down><Right>') не подойдет, поскольку событие должно сработать несколько раз и не просто на одноразовом нажатии этих кнопок, а при их удержании)
П.С: Я знаю как это всё ужасно выглядит, но…просто хотелось поиграться.



Стучу по клаве как пианист по фортепьяно.

Офлайн

#4 Май 15, 2013 10:58:49

4kpt
От: Харьков
Зарегистрирован: 2010-11-03
Сообщения: 998
Репутация: +  63  -
Профиль   Отправить e-mail  

tkinter,потоки

Вас понял. Мой вариант следующий (так оно и должно быть реализовано):
Необходимо написать метод для перехвата нажатие любой клавиши и в нем самостоятельно собирать нажатие отдельных клавиш в кластер ориентируясь на время межну нажатиями. Выходом клавиши из кластера является событие Release для одной из клавиш… Нужен пример - пишите :)

P.S. Рекомендую еще почитать это http://ru.wikiversity.org/wiki/Курс_по_библиотеке_Tkinter_языка_Python
Раздел “Класс Tk”…



Отредактировано 4kpt (Май 15, 2013 12:00:21)

Офлайн

#5 Май 16, 2013 09:49:28

Griffon
От: Ukrain, Zaporozhie
Зарегистрирован: 2009-03-04
Сообщения: 324
Репутация: +  11  -
Профиль   Отправить e-mail  

tkinter,потоки

Как-то так.

class BindCombination(object):
    _instance = None
    _combinations = list()
    _pressed_buttons = dict()
    
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = super(BindCombination, cls).__new__(cls, *args, **kw)
        return cls._instance
 #   
    def __init__(self, tk_widget, combination, func):
        """ combination - tuple("Right", "Left", "any_button") """
        for button in combination:
            tk_widget.bind("<KeyPress-%s>" % button, self._remember(button))
            tk_widget.bind("<KeyRelease-%s>" % button, self._forget(button))
            self._pressed_buttons[button] = False
        self._combinations.append({"buttons": combination,
                                   "callback": func})
 #          
    def _remember(self, button):
        def func(*args):
            self._pressed_buttons[button] = True
            self._check_combinations()
        return func
#
    def _forget(self, button):
        def func(*args):
            self._pressed_buttons[button] = False
        return func
#
    def _check_combinations(self):
        for combination in self._combinations:
            is_ok = True
            for button in combination["buttons"]:
                if not self._pressed_buttons[button]:
                    is_ok = False
                    break
            if is_ok:
                combination["callback"]()
#
# теперь в коде
#
# arrow left + arrow right
BindCombination(root, ("Left", "Right"), my_callback_function) 
# f + d
BindCombination(my_widget, ("f", "d"), my_callback_function)  
# shift + A + B
BindCombination(my_widget, ("A", "B"), my_callback_function)  



Отредактировано Griffon (Май 16, 2013 09:53:28)

Офлайн

#6 Май 16, 2013 21:27:47

4kpt
От: Харьков
Зарегистрирован: 2010-11-03
Сообщения: 998
Репутация: +  63  -
Профиль   Отправить e-mail  

tkinter,потоки

Прикольно. Но зачем

def __new__(cls, *args, **kw):
    if cls._instance is None:
        cls._instance = super(BindCombination, cls).__new__(cls, *args, **kw)
    return cls._instance



Отредактировано 4kpt (Май 16, 2013 21:28:14)

Офлайн

#7 Май 16, 2013 21:40:46

Griffon
От: Ukrain, Zaporozhie
Зарегистрирован: 2009-03-04
Сообщения: 324
Репутация: +  11  -
Профиль   Отправить e-mail  

tkinter,потоки

Изначально я думал над тем чтобы можно было добавлять и удалять бинды, и идея была держать все в одном инстансе. Боялся что при отсутствии ссылок на объекты они могут быть удалены сборщиком мусора. Возможно недопонимание.

upd: видимо лишнее : )



Отредактировано Griffon (Май 16, 2013 22:05:26)

Офлайн

#8 Май 16, 2013 21:55:48

4kpt
От: Харьков
Зарегистрирован: 2010-11-03
Сообщения: 998
Репутация: +  63  -
Профиль   Отправить e-mail  

tkinter,потоки

Просмотрел код. Класс. Стащил к себе в архив. Все хорошо, но нужно еще пилить и пилить. Я над похожей идеей думал. Я вешал обработчики Press на одни события и собирал кластер за счет разности во времени между событиями (анализ разницы проводился в атрибуте класса). Если разница маленькая - выполнялся один кластер. Но здесь есть бок. Если один из элементов кластера выключался (держал лево, зажал верх и отпустил верх, то функция, повешенная на лево уже не выполнялась и приходилось самостоятельно генерировать ее поведение, пока для нее не будет вызван Release). Именно с этой генерацией иногда и возникал бок :)

P.S. Ваше решение получше, но нужно еще подпилить одинарные события и разобраться с переопределение метода .bind. Если припечет, допилю Ваш вариант. Пока надобности нет, посему…



Офлайн

#9 Май 17, 2013 18:12:16

moron
Зарегистрирован: 2012-10-17
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

tkinter,потоки

   
 def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = super(BindCombination, cls).__new__(cls, *args, **kw)
        return cls._instance
Я плохо разбираюсь в коде это паттерн одиночки? У меня тут на 3.3 всплывала ошибка, пришлось убрать из аргументов *args, **kw внутри __new__
Ещё не понятно такое поведение: только один бинд KeyPress работает одновременно, однако KeyRelease на каждый срабатывает.
Сам я, кстати, все-таки сразу на win32api сделал слежение кнопок, правда предложенные варианты гораздо лучше(буду пользоваться предложенным). А как отслеживать нажатие клавиш в *unix, какой-нибудь модуль подобный win32api для винды?
P.S. Рекомендую еще почитать это http://ru.wikiversity.org/wiki/Курс_по_библиотеке_Tkinter_языка_Python
Раздел “Класс Tk”…
И это, блин, мне ни о чем не сказало. Лучше создавать главное окно с помощью Toplevel()? Что я должен был понять?
Всем спасибо.



Стучу по клаве как пианист по фортепьяно.

Отредактировано moron (Май 17, 2013 18:18:19)

Офлайн

#10 Май 17, 2013 21:58:37

4kpt
От: Харьков
Зарегистрирован: 2010-11-03
Сообщения: 998
Репутация: +  63  -
Профиль   Отправить e-mail  

tkinter,потоки

Однопоточность Tkinter и метод создания параллельного потока…



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version