Einen "Punkt" mit der Maus verschieben!

Fragen zu Tkinter.
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

Hi,
kann mir jemand bei einen Problem helfen?? ich möchte in ein programm was ich geschrieben habe zB. einen ausgefüllten Kreis (sozusagen einen Punkt) mit der maus verschieben können indem man die B1-Taste gedrückt hällt! weis jemand wie so was geht?? ich habe es selbst schon oft versucht aber ohne erfolg! :(

gruß meister56
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Es gibt dazu Threads im Forum. Ich meine mich an einen zu erinnern (den ich gerade nicht wiederfinde), wo es um eine Art Spielbrett ging, auf dem Figuren in Form von gefüllten Kreisen mit der Maus verschoben wurden.

Ich finde nur noch diesen Thread: http://www.python-forum.de/topic-15534.html

Allerdings geht es da um weit mehr als nur das Verschieben eines Punktes, so dass die Komplexität evtl. zu groß ist, als dass es dir wirklich hilft.
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

danke für den versuch aber du hattest recht mit deine vermutung! das ist leider echt zu Komplex um mir weiterzuhelfen!

aber ich such auch mal nach den anderen ansatz!
gruß meister56
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

meister56 hat geschrieben:danke für den versuch aber du hattest recht mit deine vermutung! das ist leider echt zu Komplex um mir weiterzuhelfen!
Also dann hier mal nur das Gerüst, das du brauchst.
Experimentier mal damit herum, ändere hier und da was und sieh dir das Ergebnis an, bis du verstanden hast, was genau hier passiert.

Code: Alles auswählen

import Tkinter as tk

class Figur(object):

    def __init__(self,canv, x,y,size=90,background="yellow",border="blue"):
        self.canv = canv
        self.x, self.y = x, y
        r = size // 2
        self.item = self.canv.create_oval(x-r, y-r, x+r, y+r,
                            fill=background,outline=border,width=2)
        self.dragged = False
        self.canv.tag_bind(self.item,"<Button-1>",self.drag)
        self.canv.tag_bind(self.item,"<ButtonRelease-1>",self.drop)
        self.canv.tag_bind(self.item,"<Enter>",self.enter)
        self.canv.tag_bind(self.item,"<Leave>",self.leave)
        self.canv.tag_bind(self.item,"<Motion>",self.move)

    def enter(self,event):
        self.canv.config(cursor="hand2")
        self.canv.itemconfig(self.item,width=4)

    def leave(self,event):
        self.canv.config(cursor="")
        self.canv.itemconfig(self.item,width=2)

    def drag(self,event):
        self.dragged = True
        self.canv.tag_raise(self.item)
        self.startx, self.starty = event.x, event.y

    def drop(self,event):
        if self.dragged:
            self.dragged = False

    def move(self,event):
        if self.dragged:
            dx, dy  = event.x-self.startx, event.y-self.starty
            self.canv.move(self.item,dx,dy)
            self.startx, self.starty = event.x, event.y
            self.x, self.y = self.x+dx, self.y+dy

if __name__ == "__main__":
    root = tk.Tk()
    canv = tk.Canvas(root, width=600, height=400)
    canv.pack()
    figur1 = Figur(canv, 100,100)
    figur2 = Figur(canv, 200, 300, size=60, background="gold", border="red")
    root.mainloop()
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

ich verstehe diese eine zeile ihrgentwie nicht!

Code: Alles auswählen

self.item = self.canv.create_oval(x-r, y-r, x+r, y+r,
                            fill=background,outline=border,width=2)
wieso wird da nochmal ein kreis erzeugt?? oder was soll das bringen??

guß meister56
BlackJack

@meister56: Was meinst Du mit "nochmal"? Da wird *der* Kreis erzeugt, den man dann bewegen kann. Wo siehst Du denn noch einen?
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

meister56 hat geschrieben:ich verstehe diese eine zeile ihrgentwie nicht!

Code: Alles auswählen

self.item = self.canv.create_oval(x-r, y-r, x+r, y+r,
                            fill=background,outline=border,width=2)
wieso wird da nochmal ein kreis erzeugt?? oder was soll das bringen??

guß meister56
Ich verstehe deine Frage nicht ganz. Was heißt "nochmal"?

Das Programm arbeitet mit Figur-Objekten auf einem Canvas. Bei der Erzeugung eines solchen Figur-Objekts (also quasi einer Figur) wird auch eine sichtbare Repräsentation des Objekts erstellt, also der Kreis, den man sehen kann. Der sichtbare Kreis IST nicht die Figur, es ist nur eine sichtbare Darstellung der Figur. Dies geschieht eben sofort bei der Instanzbildung in der __init__()-Methode.

Was es bringen soll? Nun, wenn du nichts zeichnest, gibt es zwar die Figur, es nützt dir aber nichts, weil du sie nicht siehst ...
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

achso jetzt verstehe ich es!

Code: Alles auswählen

    figur1 = Figur(canv, 100,100)
    figur2 = Figur(canv, 200, 300, size=60, background="gold", border="red") 
sind nur die figuren und das was man sieht ist dan:

Code: Alles auswählen

self.item = self.canv.create_oval(x-r, y-r, x+r, y+r,
                            fill=background,outline=border,width=2)
oder hab ich das wieder falsch verstanden??

gruß meister56
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Es sind nicht "nur" die Figuren, es sind zwei Figur-Objekte, und jedes Figur-Objekt verfügt u.a. über das Attribut item, was der Item-ID auf dem Canvas-entspricht, dem die Figur zugeordnet wurde. Über dieses Attribut item kann auf die sichtbare Gestalt von Figur zugegriffen werden.

Ein Figur-Objekt kann/ist aber mehr. Z.B. verfügt es über entsprechende Methoden, die ein drag & drop erlauben.
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

ok jetzt habe ich es gluabe ich! danke für die hilfe!
gruß meister56
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

ich habe doch noch eine frage!
was ist wenn ich schon den B1 Button für das punkterstellen benutzt habe und ihn hier auch nochmal verwende?? bei mir kommen da nämlich fehler auf!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

meister56 hat geschrieben:ich habe doch noch eine frage!
was ist wenn ich schon den B1 Button für das punkterstellen benutzt habe und ihn hier auch nochmal verwende?? bei mir kommen da nämlich fehler auf!
Ohne Code natürlich schwer zu sagen, wie du das Problem lösen kannst.

Drei Ideen dazu:
1) Für die Erzeugung die rechte Maustaste verwenden.
2) Ob ein neuer Kreis gezeichnet oder ein vorhandener bewegt werden soll, davon abhängig machen, was zwischen Drücken und Loslassen des Buttons passiert ist: Gibt es dazwischen ein move-Event, dann soll ein schon vorhandener Kreis verschoben werden; falls nicht, ein neuer erzeugt.
3) Falls ein neuer Kreis grundsätzlich außerhalb eines anderen erstellt werden soll, könnte man prüfen, ob sich der Punkt, an dem sich der Cursor zum Zeitpunkt des Buttonclick befindet innerhalb eines schon vorhandenen Kreises befindet (dann diesen bewegen) oder nicht (dann einen neuen erzeugen).
BlackJack

Dann solltest Du mal genauer beschreiben was Du da machst und was für Fehler das sind. Am besten mininmales, lauffägiges Programm zeigen, dass den Fehler enthält und den Fehler komplett per "copy'n'paste" zeigen.

Weil sonst das einzige was wir, zumindest die Nicht-Hellseher unter uns, dazu sagen können ist: "Du machst wohl irgendetwas falsch und das führt zu irgendwelchen Fehlern."
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

ok ich zeige euch mal den teil des programms wo der punkt erstellt wird und @numerix (3) das habe ich auch schon geschaft mit dem prüfen ob ein kreis in der nähe ist!

gleich kommt das programm!
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

hier ist der Teil des Programms! und ich kriegs nicht hin die beiden zu kombinieren!

Code: Alles auswählen

import Tkinter as tk


# Die Listen könnt ihr euch wegdenken! die sind nur fürs speichern! # 
punkte_namen=[]
punkte_xkoordinaten=[]
punkte_ykoordinaten=[]
punkte_farbe=[]
punkte_groese=[]

# Sonstiges #
pfarbe="black"
pgroese=3



# hier ist das Canvas-Fenster #
mainframe=tk.Frame()
mainframe.pack(fill="both",expand=1)
main=tk.Canvas(mainframe,width=605,height=700,bg="white")
main.pack(side="left")
main.bind("<Button-1>", punktz)

# Das ist die Funktion fürs Punktezeichnen #
def punktz(event):
    punkt_vorhanden=0
    punktemenge=len(punkte_namen)
    if punkt_vorhanden==0:
        for x in range(-pgroese, pgroese):
            for y in range(-pgroese, pgroese):
                xe=event.x
                ye=event.y
                xs=xe+x
                ys=ye+y
                if punktemenge!=0:
                    for i in range(1, punktemenge):
                        if xs==punkte_xkoordinaten[i]:
                            if ys==punkte_ykoordinaten[i]:
                                punkt_vorhanden=1

    
    if punkt_vorhanden!=1:
        # Name #
        name="punkt"+str(len(punkte_namen))

        # In Liste speichern #
        punkte_namen.append(name)
        punkte_xkoordinaten.append(event.x)
        punkte_ykoordinaten.append(event.y)
        punkte_farbe.append(pfarbe)
        punkte_groese.append(pgroese)
        
    
        # Punkt zeichnen #
        main.create_oval(event.x-pgroese,event.y-pgroese,event.x+pgroese,event.y+pgroese, fill=pfarbe)
BlackJack

@meister56: Und was ist/sind nun die Fehler? Also als erstes mal braucht das Ding bei auch nur halbwegs aktuellen Python-Versionen eine Kodierungsdeklaration, weil Umlaute in den Kommentaren vorkommen. Sonst kompiliert das gar nicht erst.

Dann bekommt man einen `NameError` weil `punktz` an der Stelle wo es an die Maustaste gebunden werden soll, noch gar nicht definiert ist.

Wenn man die Definition vorzieht, dann beendet sich das Programm sofort nach dem Start wieder, weil kein `Tk`-Exemplar erstellt und die Hauptschleife von `Tkinter` nicht aufgerufen wird.

Wenn man das alles behebt hat man ein Programm bei dem man mit der Maus Punkte zeichnen kann. Und nun? Wo ist das Problem? Was verhält sich da jetzt anders als erwartet?

Ansonsten noch ein paar Anmerkungen zum Quelltext. Lies Dir mal PEP8 durch und versuche Dich daran zu halten.

Python kennt die Wahrheitswerte `True` und `False` -- man sollte dafür keine Zahlen missbrauchen wie bei `punkt_vorhanden`.

Die erste Abfrage von dieser Variablen ist total überflüssig. Der Rumpf von dem ``if`` wird auf jeden Fall ausgeführt weil Du ja zwei Zeilen dafür dafür gesorgt hast, dass die Bedingung an der Stelle grundsätzlich wahr ist.

Mal davon abgesehe, das Indices bei Listen bei 0 beginnen und nicht bei 1, braucht man die überhaupt nicht um über die Elemente einer Liste zu iterieren, das kann man auch direkt machen. Wobei diese ewig verschachtelte Konstruktion um auf das Vorhandensein eines Punktes an der Stelle zu prüfen, sehr umständlich und unperformant ist. Wenn der Punkt gefunden wird, dann könnte man das zum Beispiel alles abbrechen, statt weiter zu prüfen. Dazu würde man diesen ganzen Test am besten in eine eigene Funktion auslagern, die man dann bei einem Treffer an der Stelle mit ``return True`` wieder verlässt.

Aber über alle möglichen Koordinaten zu iterieren ist sowieso eine suboptimale Lösung, man kann doch auch einfach mit *Vergleichen* testen, ob die Koordinaten des Ereignisses sich innerhalb des Rechtecks eines Punktes befinden.

Die Namensgebung könnte besser sein. Abkürzungen sollte man möglichst vermeiden.

Der Kommentar über der `punktz`-Funktion sollte durch einen DocString ersetzt werden, der die Funktion beschreibt.

Kommentare sollten zusätzliche Informationen liefern und den Sinn von Code erklären, nicht (nochmal) sagen was der Code tut. Das sieht man nämlich schon wenn man sich den Code selber durchliesst. Das betrifft zum Beispiel ``# Name #`` in der Zeile bevor `name` etwas zugewiesen bekommt. Erkenntnisgewinn durch den Kommentar ist dort gleich Null.

Der ganze Quelltext auf Modulebene sollte in einer Funktion verschwinden, und keine Funktion sollte auf globale Variablen zugreifen. Ausserdem sollten die Punkte nicht in mehreren "parallelen" Listen gespeichert werden, sondern in *einer* als Exemplare einer eigenen Punktklasse.
Benutzeravatar
krisi12345
User
Beiträge: 205
Registriert: Mittwoch 4. März 2009, 16:56
Wohnort: Das schöne München
Kontaktdaten:

Wie kann man die Kreise im ersten Beispiel auch an einem Raster ausrichten?
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Beschreib doch mal genau, was du gerne hättest, zeig am besten ein Stück Code, das zeigt, wie weit zu kommst und was nicht klappt. Dann sehen wir mal.
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

sory das ich lange nicht geantwortet habe! hatte viel um die Ohren und konnte leiden nicht lange on kommen! Morgen werde ich mal das Programm verbessern und dann poste ich die neue Version!

gruß meister56
meister56
User
Beiträge: 57
Registriert: Dienstag 23. Dezember 2008, 22:54

ich habe es glaube jetzt mit einem Freund hinbekommen!

habt ihr da noch verbesserungsvorschläge??

Code: Alles auswählen

from Tkinter import *

item_liste=[]
overlap_objekt=None
dragged=False

# x und y koordinaten
start_x=0
start_y=0

def klick(event):
    global overlap_objekt
    
    mouse_x, mouse_y=event.x, event.y
    overlap_objekt=canvas.find_overlapping(mouse_x-5, mouse_y-5, mouse_x+5, mouse_y+5)

    if overlap_objekt==():
        objekt=canvas.create_oval(mouse_x-4, mouse_y-4, mouse_x+4, mouse_y+4, fill='black')
        canvas.tag_bind(objekt, '<Button-1>', drag)
        canvas.tag_bind(objekt, '<ButtonRelease-1>', drop)
        canvas.tag_bind(objekt, '<Enter>', enter)
        canvas.tag_bind(objekt, '<Leave>', leave)
        canvas.tag_bind(objekt, '<Motion>', move)
        return
        

def drag(event):
    global dragged, start_x, start_y
    start_x, start_y=event.x, event.y
    dragged=True
    canvas.config(cursor='fleur')

def drop(event):
    global dragged
    dragged=False
    canvas.config(cursor='')
    print event.x, event.y

def move(event):
    global overlap_objekt, start_x, start_y, dragged
    if dragged:
        vektor=(event.x-start_x, event.y-start_y)
        canvas.move(overlap_objekt, vektor[0], vektor[1])
        start_x, start_y=event.x, event.y
        
def enter(event):
    canvas.config(cursor='hand2')

def leave(event):
    canvas.config(cursor='')


# Tk objekte
fenster=Tk()
canvas=Canvas(fenster, width=400, height=400)
canvas.pack()

# binds
canvas.bind('<Button-1>', klick)
Antworten