Buttonklick simulation

Fragen zu Tkinter.
Antworten
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Hallo
Für eines meiner Programme würde ich gerne Buttonklicks simulieren. Das heißt, dass der Button (wie bei einem Klick mit der linken Maustaste) der Button heruntergedrückt wird, und dann wieder zurückspringt...

Suche im Internet und hier im Forum hat keine Ergebnisse gebracht, obwohl ich dachte schon mal bei einer anderen Suche soetwas gelesen zu haben... Kann sein, dass es "Tastenklicks simulieren" war, was ich damals las.

Viele Grüße Markus :-)
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Markus12

Was es nicht gibt macht man selber.

Hier meine Variante basierend auf einem Canvas-Widget:

Code: Alles auswählen

# Skriptname button_click_sim_02.py (wuf)

import Tkinter as tk

class MyButton(tk.Canvas):

    def __init__(self, parent, text='MyButton', bd=1, relief='raised',
        highlightthickness=0, **options):

        tk.Canvas.__init__(self, parent, bd=bd, relief=relief,
            highlightthickness=highlightthickness, **options)

        self.width, self.height = options['width'], options['height']
        self.bd = bd

        self.create_text(self.width//2, self.height//2, text=text,
            anchor='center', tags='text')

        self.bind('<Button-1>', self.button_press)
        self.bind('<ButtonRelease-1>', self.button_release)

        self.state = False

    def button_press(self, event=None):
        if self.state: return
        self.config(relief='sunken')
        self.move('text', self.bd, self.bd)
        self.state = True

    def button_release(self, event=None):
        if not self.state: return
        self.config(relief='raised')
        self.move('text', -self.bd, -self.bd)
        self.state = False

    def change_state(self):
        if self.state:
            self.button_release()
        else:
            self.button_press()

def button_click_simulation():
    button.change_state()
    app_win.after(500, button_click_simulation)


def button_callback():
    print 'Button'

app_win = tk.Tk()
app_win.config(bg='steelblue3')

button = MyButton(app_win, width=200, height=30)
button.pack(padx=10, pady=10)

button_click_simulation()

app_win.mainloop()


Gruss wuf :wink:
Take it easy Mates!
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Hallo wuf,
Du brauchst doch aber gar nicht den Button
mit einem Canvas simulieren.
Nimm doch direkt ein Button-Widget.
Damit ersparst Du Dir auch das "moven" des Textes.

http://paste.pocoo.org/show/116682/

:wink:
yipyip
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo yipyip

Superlösung! Wusste gar nicht, dass sich die Text-Position auf dem Button rein durch das ändern der 'relief'-Option beeinflussen lässt. Das vereinfacht die Lösung natürlich.

Danke dir für den Tipp.

Gruss wuf :wink:
Take it easy Mates!
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Danke.
Soweit war ich aber auch schon :P
Mit after und allem dazugehörigen hatte es auch funktioniert, bis darauf, dass ich in einer Schleife der Funktion sagte, hintereinander die Buttons zu drücken und wieder loszulassen, aber da after einen Thread bildet, wurden alle Buttons zur gleichen Zeit heruntergedrückt und wieder losgelassen, was nicht dem Sinn entsprach. Auch dafür habe ich eine Lösung gefunden, aber die funktioniert nicht gut.

Und da ich dachte von einer Funktion gelesen zu haben, fragte ich hier nach ;)

Meine Lösung:

Code: Alles auswählen

class Zahlenfeld(Frame):
     def __init__(self, master, **cnf):
          self.kombination = []
          self.buttons = {}
          opt = {"pad": 0, "zpadx": 10, "zpady": 10, "zbg": "grey", "bd": 5, "font": ("Arial", 15, "bold")}
          keys = opt.keys()
          opt.update(cnf)
          for i in keys:
               setattr(self, i, opt[i])
               del opt[i]
          Frame.__init__(self, master, opt)
          self.erstellen()
          
     def erstellen(self):
          row = 0
          col = 0
          for i in range(1, 10):
               b = Button(self, font = self.font, text = i, padx = self.zpadx, pady = self.zpady, bg = self.zbg, bd = self.bd)
               b.grid(row = row, column = col, padx = self.pad, pady = self.pad)
               b.bind("<Button-1>", self._handler)
               self.buttons[i] = b
               if not (i%3):
                    row += 1
                    col = 0
               else:
                    col += 1

     def _handler(self, e):
          self.hinzufuegen(e.widget.cget("text"))
          
     def hinzufuegen(self, nummer):
          self.kombination.append(nummer)

     def wiederholen(self):
          for i in self.kombination:
               self._druecken(self.buttons[i], 1)

     def _druecken(self, button, n):
          if n:
               button.config(relief="sunken")
               button.after(600, lambda: self._druecken(button, 0))
               thread = Thread(target = self._zeit)
               thread.start()
               thread.join()
          else:
               button.config(relief="raised")
          button.update()

     def _zeit(self):
               time.sleep(0.5)


class Zahlenfenster(Tk):
     def __init__(self, toolfenster = True, vordergrund = False, erscheinen = False, verschwinden = False):
          self.toolfenster = bool(toolfenster)
          self.vordergrund = bool(vordergrund)
          self.erscheinen = bool(erscheinen)
          self.verschwinden = bool(verschwinden)
          self.tempo = 0.04
          
          Tk.__init__(self)
          self.title("Bitte geben sie ihre Kombination ein!")
          self.attributes("-toolwindow", toolfenster)
          self.attributes("-topmost", vordergrund)
          self.protocol("WM_DELETE_WINDOW", self.schliessen)
          self.resizable(0,0)

          self.zahlenfeld = Zahlenfeld(self, pad=1, font=("Arial", 30, "bold"), bg="orange", zbg="white")
          self.zahlenfeld.pack()

          self.focus_force()
          #self.mainloop()

if __name__ == "__main__":
     a = Zahlenfenster(erscheinen = 1, verschwinden = 1, vordergrund = 1)
Graphisch noch nicht gut, aber ich bin ja auch erst am Algorithmus dran ;)

Komische Sache ist, dass wenn die Zeit in der after-Funktion größer ist als die in dem Thread, welcher time.sleep ausführt, dass es dann funktioniert, aber umgedreht nicht mehr... Da wird nicht einmal ein Button heruntergedrückt? Hängt das mit dem Thread zusammen?

Grüße Markus
BlackJack

@Markus12: Was veranstaltest Du bei `Zahlenfeld` bloss mit dem `opt`-Dictionary!? In Zeile 11 ist das garantiert *leer*, also als Argument überflüssig. Und den ganzen Inhalt dynamisch als Attribute an das Objekt binden, kann man auch einfacher und direkter haben. Sollte man aber nicht, denn letztendlich sind das ja *Argumente* die in `erstellen()` Verwendung finden. So sollte man sie da auch übergeben.

Die Aktion bei `Button`\s sollte man nicht mit `bind()` auf die Maustaste als Ereignis, sondern mit dem `command`-Argument machen, sonst verhalten sich die Schaltflächen nicht so, wie der Benutzer das gewohnt ist.

Das mit dem `Thread` ist eine total umständliche Art ``time.sleep(0.5)`` zu schreiben. Wenn man einen Thread startet und dann *sofort* danach solange wartet, bis er zuende ist, kann man sich den Thread auch sparen.

Letztlich sollte man das ohne Thread und ohne `sleep()`, nur mit `after()` lösen. Da muss man das "Denken in Schleifen" verlassen und bei jedem Aufruf durch `after()` überlegen welcher *eine* kleine Schritt zu tun ist, und nach diesem Schritt mit `after()` dafür sorgen, dass nach einer bestimmten Zeit der nächste Schritt getan wird.

`_druecken()` verwendet eine Zahl als Wahrheitswert, wo's doch extra `True` und `False` gibt.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

opt ist ein dictionary mit vordefinierten optionen, die das herkömmliche Frame nicht verwendet. Dieses dictionary wird zwei Zeilen später mit den übergebenen cnf-optionen überschrieben, denn es kann gut sein, dass beim initialisieren von Zahlenfeld optionen wie "pad" oder "zpadx" übergeben werden. Damit werden die von mir in opt vordefinierten werte überschrieben. und in variablen gespeichert. Gleichzeitig werden sie allerdings auch wieder aus opt gelöscht, denn meine eigenen optionen können nicht vom Frame-Widget gelesen werden und es würden Exceptions entstehen.

Für mich macht das Sinn, ich verstehe nicht, was du meinst. Es funktioniert, daher sollte es richtig sein.

mit bind habe ich wohl einfach geschlafen, mir ist nun, wo du's sagst bewusst, dass ich ´´command´´ verwenden sollte. Ich hatte es vergessen zu ändern, nachdem ich einen anderne Lösungsweg zuerst gegangen war, der nicht funktionierte, und da muss ich es wohl übersehen haben.

Keine ahnung wie man es sonst mit after löst. Habe zwei Beiträge oben drüber ja mein Problem geschildert gehabt, warum es nicht funktioniert, und keinen anderen Weg gefunden.

1 ist True und 0 ist False, und daher habe ich einfach die Zahlen verwendet anstatt die Wahrheitswerte, was auf's selbe rauskommt ;)
Grüße Markus
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Nein, warte, ich hatte ´´bind´´ verwendet, damit die Funktion erkennen kann, welcher Button gedrückt wurde! Hatte also seinen Sinn :D
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Ok ok ok, habe das wiederholen-Problem von alleine gelöst.

Neuer Code mit Änderungen soweit:

Code: Alles auswählen

from Tkinter import*

class Zahlenfeld(Frame):
     def __init__(self, master, **cnf):
          self.kombination = []
          self.buttons = {}
          opt = {"pad": 0, "zpadx": 10, "zpady": 10, "zbg": "grey", "bd": 5, "font": ("Arial", 15, "bold")}
          keys = opt.keys()
          opt.update(cnf)
          for i in keys:
               setattr(self, i, opt[i])
               del opt[i]
          Frame.__init__(self, master, opt)
          self.erstellen()
          
     def erstellen(self):
          row = 0
          col = 0
          for i in range(1, 10):
               b = Button(self, font = self.font, text = i, padx = self.zpadx, pady = self.zpady, bg = self.zbg, bd = self.bd)
               b.grid(row = row, column = col, padx = self.pad, pady = self.pad)
               b.bind("<Button-1>", self._handler)
               self.buttons[i] = b
               if not (i%3):
                    row += 1
                    col = 0
               else:
                    col += 1

     def _handler(self, e):
          self.hinzufuegen(e.widget.cget("text"))
          
     def hinzufuegen(self, nummer):
          self.kombination.append(nummer)

     def wiederholen(self):
          self._druecken(0, True)

     def _druecken(self, zahl, n):
          if len(self.kombination) > zahl:
               nummer = self.kombination[zahl]
               button = self.buttons[nummer]
               if n:
                    button.config(relief="sunken")
                    button.after(200, lambda: self._druecken(zahl, False))
               else:
                    button.config(relief="raised")
                    button.after(300, lambda: self._druecken(zahl+1, True))


class Zahlenfenster(Tk):
     def __init__(self, toolfenster = True, vordergrund = False, erscheinen = False, verschwinden = False):
          self.toolfenster = bool(toolfenster)
          self.vordergrund = bool(vordergrund)
          self.erscheinen = bool(erscheinen)
          self.verschwinden = bool(verschwinden)
          self.tempo = 0.04
          
          Tk.__init__(self)
          self.title("Bitte geben sie ihre Kombination ein!")
          self.attributes("-toolwindow", toolfenster)
          self.attributes("-topmost", vordergrund)
          self.protocol("WM_DELETE_WINDOW", self.schliessen)
          self.resizable(0,0)

          self.zahlenfeld = Zahlenfeld(self, pad=1, font=("Arial", 30, "bold"), bg="orange", zbg="white")
          self.zahlenfeld.pack()

          if self.erscheinen:
               self.aufbauen()
          self.focus_force()
          #self.mainloop()

     def aufbauen(self):
          self.attributes("-alpha", 0.0)
          self._aufbauen2()

     def _aufbauen2(self):
          alpha = self.attributes()[1]
          if self.erscheinen and alpha < 1.0:
               self.attributes("-alpha", alpha + self.tempo)
               self.after(1, self._aufbauen2)
               
     def schliessen(self):
          self.attributes("-alpha", 1.0)
          self._schliessen2()
          
     def _schliessen2(self):
          alpha = self.attributes()[1]
          if self.verschwinden and alpha > 0.0:
               self.attributes("-alpha", alpha - self.tempo)
               self.after(1, self._schliessen2)
          elif not self.verschwinden:
               self.withdraw()
          
                    
if __name__ == "__main__":
     a = Zahlenfenster(erscheinen = 1, verschwinden = 1, vordergrund = 1)
Grüße Markus
BlackJack

@Markus12: `bind()` ist trotzdem der falsche Weg, wenn man `Button`\s haben möchte, die sich so verhalten, wie der Benutzer das erwartet. Und sich den Wert aus der Anzeige der GUI zu holen ist auch nicht besonders sauber.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Ok, danke für die kleine Hilfe.
Ich kenne allerdings keinen Ausweg, wie die Funktion herausfinden soll, welcher Button gedrückt wurde...

Ich habe ja nun die Sache mit dem Wiederholen gelöst, mich würde aber interessieren, wie du, Blackjack, es gelöst hättest :)

Viele Grüße Markus :)
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Markus 12 !

So ganz habe ich nicht verstanden was du machen möchtest. Könnte dir das helfen ?

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8

import Tkinter as tk


class Dialer(tk.Frame):
    def __init__(self, master, font, number, width=2, height = 2, time = 300):
        tk.Frame.__init__(self, master, width = width, height = height)
        
        self.counter = 0
        self.run = False
        self.font = font
        self.width = width
        self.height = height
        self.loop_time = time
        self.number = number
        self.button_press = True
        self.dialing = True
        self.buttons = list()
        row = 0
        col = 0
        
        for i in xrange(9):
            button = tk.Button(self, font = self.font, text = i+1)
            button.grid(row = row, column = col, padx=3, pady=3)
            self.buttons.append(button)
            if col == 2:
                col = -1
                row += 1
            col += 1
            
            
    def dial_number(self):
        if self.dialing:
            if self.button_press:
                self.buttons[self.number[self.counter]-1].config(relief = "sunken")
                self.button_press = False
            else:
                self.button_press = True
                self.buttons[self.number[self.counter]-1].config(relief = "raised")
                self.counter += 1
            if self.counter == len(self.number):
                self.dialing = False
            self.master.after(self.loop_time, self.dial_number)
            
           
class Gui(object):
    
    FONT = "Arial 10 bold"
    def __init__(self, root):
        self.root = root
        self.status_check = True
        self.gui()
        
            
    def gui(self):
        self.dialer = Dialer(self.root, self.FONT, list((1, 4, 5, 9, 5, 6, 3)))
        self.dialer.pack()
        self.dialer.dial_number()
        self.check_status()
        
      
    def check_status(self):
        if self.status_check:
            if self.dialer.dialing == False:
                self.dialing = False
                print "END"
                self.status_check = False
                self.dialer.pack_forget()
            self.root.after(10, self.check_status)
         
        
def main():
    root = tk.Tk()
    app = Gui(root)
    root.resizable(0, 0)
    root.title("Dialer")
    root.mainloop()
        
if __name__ == '__main__':
    main()
gruß frank
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Danke kaytec,
Aber ich hatte mein Problem selbst gelöst ;)
Danke, Blackjack, habe mir deinen code angesehen, und ja, habe noch eingebaut, dass man nicht während einer laufenden wiederholung eine zweite starten kann, danke für den tipp.

Grüße Markus :)
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo !

Ich wollte auch nochmal: http://paste.pocoo.org/show/117293/

Gruß Frank
Antworten