Position eines Buttons abfragen(grid-manager)

Fragen zu Tkinter.
Antworten
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Hi,
ich versuche gerade ein Spielfeld zu erstellen, bei dem man z.B. auf einen der Buttons klickt und dort dann ein anderes bild angezeigt wird. Mein Problem ist dass ich das ganze mit dem grid manager "gliedere" und ich nicht weiß wie ich die position abfragen kann wenn auf den button geklickt wird. ( Position in form von row und column)

Hier der Codeschnipsel:

Code: Alles auswählen

from tkinter import *

root = Tk()
icon = PhotoImage(file="icon.gif")
f = Frame(root).grid(row=0, column=0)
for x in range(0, 5):
    for y in range(0, 5):
        Button(f, relief="flat", image = icon,bd = 0, height=30, width=30).grid(row=x, column=y)
        
mainloop()
Danke im vorraus
BlackJack

@misterjosu: Nachträglich ermitteln an welcher Stelle sich die Schaltfläche befindet würde man eher nicht machen. Stattdessen sollte das der Rückruffunktion bei `command` bekannt gemacht werden. Zum Beispiel mit der `functools.partial()`-Funktion (Achtung: Python 2.x):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function
import Tkinter as tk
from functools import partial


def print_location(x, y):
    print('Pressed button at:', x, y)


def main():
    root = tk.Tk()
    frame = tk.Frame(root).grid(row=0, column=0)
    for y in range(5):
        for x in range(5):
            tk.Button(
                frame,
                text='x',
                command=partial(print_location, x, y)
            ).grid(row=y, column=x)
    root.mainloop()


if __name__ == '__main__':
    main()
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Danke, hat geklappt.Jetzt habe ich allerdings das Problem dass ich die .bind Methode nicht auf ein Frame anwenden kann. Kann mir bitte jemand erklären warum es nicht funktioniert ?

Wenn jemand zeit und lust hat könnte er mir dann erklären wie das mit den Events genau funktioniert.

Hier eine Website von der ich das mit den Events "gelernt" hab: http://www.pythonware.com/library/tkint ... ndings.htm

Code: Alles auswählen

from tkinter import *

def mouse_left(event):
    grid_info = event.widget.grid_info()
    x = grid_info["row"]
    y = grid_info["column"]
    Button(raster, relief="flat", image = icon_0, bd = 0, height=30, width=30).grid(row=x, column=y)
    
def mouse_right(event):
    grid_info = event.widget.grid_info()
    x = grid_info["row"]
    y = grid_info["column"]
    Button(raster, relief="flat", image = icon_1, bd = 0, height=30, width=30).grid(row=x, column=y)
    
root = Tk()
area_len = 20

icon_0 = PhotoImage(file="icon.gif")
icon_1 = PhotoImage(file="icon1.gif")
icon_2 = PhotoImage(file="icon2.gif")
raster = Frame(root)


Label(raster, width=30, text="Select Item").grid(row=0, column=+area_len)


for x in range(0, area_len):
    for y in range(0, area_len):
        Button(raster, relief="flat", image = icon_0, bd = 0, height=30, width=30, command=lambda:mouse_right).grid(row=x, column=y)


raster.grid(row=0, column=0)

raster.bind("<Button-3>", mouse_left)
raster.bind("<Button-1>", mouse_right)
mainloop()
Danke im Voraus
BlackJack

@misterjosu: Was heisst „nicht funktioniert” denn genau? Wenn Du das an den `Frame` bindest, dann musst Du auch auf den Frame klicken, und nicht zum Beispiel auf die `Button`\s die darauf dargestellt werden. Allerdings macht das `grid_info()` da wenig Sinn, denn der `Frame` steckt ja nicht in einem Grid also gibt es da auch keine vernünftigen Werte. Das müsstest Du auf den `Button`\s machen, allerdings ist das nicht sauber solche Informationen aus der GUI abzufragen. Die ist zur Kommunikation mit dem Benutzer da und nicht damit man Daten aus der Programmlogik darin speichert. Wie man an `x` und `y` kommt, habe ich doch mit `partial()` gezeigt.

Du kannst ausserdem nicht ständig neue `Button`-Objekte erzeugen. Ich weiss gar nicht, ob das *überhaupt* zuverlässig auf jeder Plattform funktioniert, auf jeden Fall ist das ein Speicherleck und dürfte mit der Zeit auch die GUI immer langsamer machen. Verändere an der Stelle einfach das vorhandene Widget, zum Beispiel in dem Du das Bild auf dem bestehenden `Button` auswechselst.

Falls das immer noch mit dem Game of Life in Verbindung steht und Du mit der Maus Zellen zum Leben erwecken oder töten willst, dann würde ich ganz normal das `command` von `Button` benutzen und da eine Funktion/Methode dran binden, die den Zustand der Zelle zwischen den beiden Werte umschaltet. Für einen Ansatz bei dem je eine Maustaste für einen Zustand zuständig ist würde ich keine `Button`\s verwenden, denn das entspricht nicht dem Verhalten was der Anwender von diesem Widget erwartet. Da könnte man dann `Frame`\s für verwenden.

Diese ganzen globalen Werte sind unsauber. Hier sollte man OOP verwenden.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Ok. das hat nichts mit dem Geme of life zutun, ist nur um das mit der Gui besser zu begreifen.Wie erreiche ich es dass ich auf jeden der buttons klicken kann(mit der rechten und der linken) und dann was anderes passiert ? Ich wollte jetzt dass alle buttons in einem frame sind und wennich die bind option auf den frame anwende dachte ich dass dass dann auch für die objekte in ihm gelten

PS. wie könnte ich in diesem Fall OOP anwenden ?
BlackJack

@misterjosu: Wie gesagt würde ich für so etwas keine Buttons verwenden, denn da hat der Anwender gewisse Erwartungen wie die sich verhalten, was man mit einem einfachen `bind()` nicht so leicht nachmachen kann. Insbesondere kommt bei der linken Maustaste ja zusätlich noch das normale Verhalten von Schaltflächen zum Tragen was bei der rechten Maustaste anders aussieht.

OOP wendet man in diesem Fall genau so an wie man das ausserhalb von GUIs machen würde um globale Variablen zu vermeiden. Darum bin ich ja auch der Meinung, dass man OOP drauf haben sollte *bevor* man mit GUI-Programmierung anfängt.

Man könnte zum Beispiel eine Klasse von `Frame` ableiten, die die Bilder für die Icons kennt und die Felder in dem Gitter erstellt und mit einer Methode verbindet die dann die entsprechende Änderung an dem angeklickten Feld vornimmt. Und auch an dem Datenmodell aus der Programmlogik, wie auch immer das aussehen mag. So eine Trockenübung nur mit GUI ohne das mit einer Programmlogik zu verbinden erscheint mir etwas sinnfrei.
Antworten