Canvas

Fragen zu Tkinter.
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

Hallo.
Ich habe das Internet schon einigermaßen durchforstet (was sehr zeitaufwändig war) und habe noch nichts passendes gefunden. Desshalb dachte ich mir ich stelle meine fragen jetzt hier und nich mehr an google ;)
Könnt mir jemand für python 2.7 ein "Tutorial" geben, wie man ein canvas fenster erstellt und darin striche und kreise (ovale) an einem bestimmten ort erstellt (ich bin grad an einem Musik-Notationprogramm dran ;) ) oder auch soetwas wie einen Notenschlüssel.

Danke :)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Tkinter Dokumentation und ein paar kleine Beispiele findet man auf effbot

Ich hatte auch schon mal ein kleines Programm* geschrieben und irgendwo geistert in dem Forum auch noch ein Thread zu Notationsprogramme herum.

*clef ist in dem Programm der Notenschlüssel und wird als weiches(smooth) Polygon gezeichnet.

Edit: Thread gefunden, der war ja von dir :shock:
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

danke.
ja ich weis. aber ich bräucht da iwie ne erklärung dazu. Weil ich bin Einsteiger und kapier das ganze noch nicht so. Da das für die schule ist kann ichs auch nicht einfach rauskopieren..
Einfach nur welcher teil was macht oder so. Desshalb ja auch tutorial :)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Wozu genau brauchst du eine Erklärung, zu meinem Programm (das kann ich durchaus verstehen :D ) oder zu der effbot-Seite?

Du legst dir einfach wie im Link beschrieben einen Canvas an und dann zeichnest du darauf mit den verschiedenen Funktionen. Zum Beispiel mit "create_line" dieses verlangt die Parameter "coords" das sind 4 an der Zahl - x0, y0, x1, y1 - also die Koordinaten vom ersten Punkt und die Koordinaten vom zweiten Punkt. Zwischen den zwei Punkten wird dann die Linie gezeichnet. Alles andere sind Zusatz-Optionen wie z.B. ob Pfeile an den Linienenden gezeichnet werden sollen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

ja also ich finde das programm sieht schon sehr gut aus ;) deshalb wenn mir das weiterhelfen kann eine erklärung zu dem programm :)
die effbot seite versteh ich schon :) aber was ich nicht finde ist wie ich das canvas-fenster genau plazieren kann.

außerdem würd ich gerne (ich weis nicht ob das sinvoll ist) da die Darstellung nur ein kleiner Teil vom Programm ist das Gesammte gut unterteilen.
ich hab mal erst ein Darstellungsfenster als Frame dargestellt (nicht durch ein Canvas Fenster)

Code: Alles auswählen

import Tkinter as tk

class Darstellungsfenster():
     def __init__(self,master,xpos,ypos,width,height):

        #~~ Initialisierung der geerbten Tk-Frame-Klasse
        tk.Frame.__init__(self,master,
            border = 4,
            relief = 'ridge',
            background = 'White'
            )
        self.place(x=xpos,y=ypos,width=width,height=height)
        
        
    
root=tk.Tk()

Programmbreite      = 800
Programmhoehe       = 600
ProgrammX_Position  = 0
ProgrammY_Position  = 0

root.geometry('%dx%d+%d+%d' % (Programmbreite,
                               Programmhoehe,
                               ProgrammX_Position,
                               ProgrammY_Position))
Programmname = 'Notationsprogramm'
root.title(Programmname)

# Position des Darstellungsfensters
xpos    = 80
ypos    = 80
width   = 715
height  = 515    
Darstellungsfenster_object = Darstellungsfenster(root,xpos,ypos,width,height)

root.mainloop();
so wollte ich das ganze einteilen, nur eben mit Canvas Darstellung..
Wie erstelle ich dann eine Klasse vom typ canvas das sich an dieser Position initialisiert?
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

ich habs jetz einfach mal versucht und es ging:
reicht es nur die 2 wörter "Frame" mit "Canvas" zu ersetzen?
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

achja in die Klasse Darstellungsfenster gehört:

Code: Alles auswählen

class Darstellungsfenster(tk.Frame):
und jetz hald

Code: Alles auswählen

class Darstellungsfenster(tk.Canvas):
oder? :)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich werde mal mein Programm dokumentieren und dann nochmal hochladen.

Zu deinem Problem:
- Arbeite nicht mit "place"-Methoden das ist sowas von altmodisch und ohne Designer reine Folter. Nimm am besten nur "pack"-Methoden und sie mal wie weit du kommst.
- Lass niemals das Widget sich selbst auf sein Eltern-Widget packen. So kann man als Nutzer von diesem Widget nicht mehr den Geometry-Manager bestimmen.
- Man kann es nicht oft genug sagen: Halte dich an PEP8.

So könnte man es z.B. machen:

Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk

class DarstellungsFenster(tk.Canvas):

     def __init__(self, master, cnf={}):
        # bedenke das border, relief und background so Standard sind
        # und deine Werte die du in 'cnf' übergibst eventuell überschreibst
        tk.Canvas.__init__(self, master, cnf, border=4, relief='ridge',
                           background='white')


def hauptprogramm():
    wurzel = tk.Tk()
    breite, hoehe = 800, 600
    x, y = 0, 0
    wurzel.geometry('%dx%d+%d+%d' % (breite, hoehe, x, y))
    wurzel.title('Notationsprogramm')
    darstellungsfenster = DarstellungsFenster(wurzel, dict(width=715, height=515))
    # expand sorgt dafuer das dein Widget immer genauso gross ist wie dein 
    # wurzel-Fenster
    darstellungsfenster.pack(expand=True, fill="both")

# Sonst wird der Code auch bei einem Import aufgerufen 
if __name__ == "__main__":
    hauptprogramm()
Da die Dokumentation bei effbot ein paar Stellen leider etwas unschön ist, hier mal noch eine schnelle Kurzbeschreibung von mir.

"position" - ist immer ein Punkt der bei Bildern oder Widgets immer die obere linke Ecke repräsentiert
"coords" - sind zwei oder mehrere Punkte die miteinander verbunden werden
"bbox" - sind immer zwei Punkte die ein Rechteck bilden, also ein Punkt für links oben und einen für rechts unten. In diesem Rechteck
Der Rest sind optionale Parameter und können mit func(..., keyword0=value, keyword1=value) der Funktion übermittelt werden.

So stellt sich nun folgendes zusammen:
# positions
create_bitmap(x, y) - erzeugt eine Bitmap

create_text(x, y) - erzeugt einen Text

create_window(x, y) - erzeugt ein widget auf dem Canvas, z.B. nötig wenn du die Buttons mit dem Canvas ausdrucken möchtest.

create_image(x, y) - erzeugt ein beliebiges Bild, unterschied zur Bitmap ist nur das man jedes Bildformat nutzen kann, wenn man "PIL.ImageTk"-Objekte nimmt. Die Bitmap kann zudem noch beim zeichnen verändert werden z.B. Vordergrundfarbe

# coords
create_line(x0, y0, x1, y1): - hatte ich oben schon beschrieben
create_polygon(x0, y0, x1, y1,..., xn, yn) - akzeptiert beliebig viele Punkte die in der Reihenfolge, in welcher man sie übergibt verbunden werden.

# bbox
create_arc(x0, y0, x1, y1) - zeichnet einen Kreisausschnitt/bogen in das Rechteck was durch die Punkte ensteht. "start" und "extent" beschreiben wo der Kreis beginnen und um wie viel Grad er sich neigen soll. z.B. create_arc(10, 10, 110, 110, start=0, extent=180) würde einen 100 Pixel im Durchmesser großen oberen Halbkreis erzeugen. Zu beachten ist 0 entspricht Rechts nicht oben und der Winkel ist entgegen dem Uhrzeigersinn.

create_rectanglel(x0, y0, x1, y1) - sollte ja jetzt nicht schwer fallen

create_oval(x0, y0, x1, y1) - und hier nochmal genau das gleiche nur das ein Oval in der bbox erzeugt wird. Die Box ist einfach nur die Begrenzung. Hier ist "joinstyle" für die Noten eventuell interessant die Möglichkeiten sind dort "miter", "bevel", oder "round", welches Standard ist. Damit kann man bestimmen wie die Punkte die bei zeichnen des Ovals erzeugt werden verbunden werden sollen.

Es gibt noch zahlreiche andere Optionen die mit angegeben werden können, hierzu sieht man am besten direkt in die Tk-Dokumentation.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

danke :)
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

ich versteh nicht wie du das mit dem umformen gemeint hast. zumindest funktioniert das bei mir nicht..das programm schaut nicht so aus wie es vorher war und die Methoden (draw_takt) kann ich auch nicht mehr ausführen. wo liegt da dann der Fehler?

Jetzt:

Code: Alles auswählen

import Tkinter as tk

class Darstellungsfenster(tk.Canvas):
    def __init__(self,master,cnf={}):

        #~~ Initialisierung der geerbten Tk-Frame-Klasse
        tk.Canvas.__init__(self,master,
            border = 4,
            relief = 'ridge',
            background = 'White'
            )
        
        self.letzterTakt = 0
        self.zeile = 0
    
    def left(self):
        x = 50 + (self.letzterTakt * 200)
        return x
    
    def right(self):
        x = 250 + (self.letzterTakt * 200)
        return x
        
    def draw_key(self):
        print 5
        
    def draw_measure(self,y):
        self.create_line(self.right(), y, self.right(), y-(4*7))
    
    def draw_takt(self,y=130):
        self.pos_y=y + (self.zeile*60)
        for i in xrange(5):
            y = self.pos_y +(i * 7)
            self.create_line(self.left(), y, self.right(), y)
        if (self.letzterTakt==0):
            self.draw_key()
        self.draw_measure(y)
        if (self.letzterTakt==3):
            self.letzterTakt=0
            self.zeile=self.zeile + 1
        else:
            self.letzterTakt = self.letzterTakt + 1
        

        
        
    
def hauptprogramm():
    root=tk.Tk()

    Programmbreite      = 1000
    Programmhoehe       = 700
    ProgrammX_Position  = 0
    ProgrammY_Position  = 0

    root.geometry('%dx%d+%d+%d' % (Programmbreite,
                               Programmhoehe,
                               ProgrammX_Position,
                               ProgrammY_Position))
    Programmname = 'Notationsprogramm'
    root.title(Programmname)

    # Position des Darstellungsfensters
    xpos    = 80
    ypos    = 80
    width   = 915
    height  = 615    
    w = Darstellungsfenster(root, dict(width=width,height=height))
    w.pack(expand=True, fill= "both")
    root.mainloop()

if __name__=="__main__":
    hauptprogramm()

wurst.draw_takt()
w.draw_takt()
w.draw_takt()

Vorher:

Code: Alles auswählen

import Tkinter as tk

class Darstellungsfenster(tk.Canvas):
    def __init__(self,master,xpos,ypos,width,height):

        #~~ Initialisierung der geerbten Tk-Frame-Klasse
        tk.Canvas.__init__(self,master,
            border = 4,
            relief = 'ridge',
            background = 'White'
            )
        
        self.letzterTakt = 0
        self.place(x=xpos,y=ypos,width=width,height=height)
        self.zeile = 0
    
    def left(self):
        x = 50 + (self.letzterTakt * 200)
        return x
    
    def right(self):
        x = 250 + (self.letzterTakt * 200)
        return x
        
    def draw_key(self):
        print 5
        
    def draw_measure(self,y):
        self.create_line(self.right(), y, self.right(), y-(4*7))
    
    def draw_takt(self,y=130):
        self.pos_y=y + (self.zeile*60)
        for i in xrange(5):
            y = self.pos_y +(i * 7)
            self.create_line(self.left(), y, self.right(), y)
        if (self.letzterTakt==0):
            self.draw_key()
        self.draw_measure(y)
        if (self.letzterTakt==3):
            self.letzterTakt=0
            self.zeile=self.zeile + 1
        else:
            self.letzterTakt = self.letzterTakt + 1
        

        
        
    
root=tk.Tk()

Programmbreite      = 1000
Programmhoehe       = 700
ProgrammX_Position  = 0
ProgrammY_Position  = 0

root.geometry('%dx%d+%d+%d' % (Programmbreite,
                               Programmhoehe,
                               ProgrammX_Position,
                               ProgrammY_Position))
Programmname = 'Notationsprogramm'
root.title(Programmname)

# Position des Darstellungsfensters
xpos    = 80
ypos    = 80
width   = 915
height  = 615    
w = Darstellungsfenster(root,xpos,ypos,width,height)


w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()
w.draw_takt()


root.mainloop();
BlackJack

@questlove: Mach Dir mal klar an welcher Stelle im Programmablauf bei den beiden Varianten die Hauptschleife von `Tkinter` aufgerufen wird. Dieser Aufruf kehrt erst zurück wenn das Programmfenster geschlossen wird.
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Ich habe mit Turtle angefangen zu malen, danach kannst du dir einfacher vor Augen führen, wie die Befehle bei Canvas auszusehen haben.

Tipp: Nehme die interaktive Console und mach folgendes:

Code: Alles auswählen

>>> import turtle
>>> pen = turtle.Pen()
>>> pen.write("Hello")
>>> pen.color("black")
>>> pen.color("orange")
>>> pen.down()
>>> pen.forward(20)
>>> pen.left(100)
>>> pen.forward(100)
>>> pen.color("green")
>>> pen.left(130)
>>> pen.forward(200)
>>> for i in range(20):
...     pen.forward(10)
...     pen.left(20)
...     pen.up()
...     pen.forward(20)
...     pen.down()
...     pen.right(180)
...     pen.forward(30)
... 
>>> 
Das zeigt dir auf simpelster Weise, wie man "malt". Fast genauso malt man mit Canvas, nur eben nicht live sondern statisch!
LG Maik
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo questlove

So funktioniert w.draw_takt() wieder:

Code: Alles auswählen

def hauptprogramm():
    root=tk.Tk()

    Programmbreite      = 1000
    Programmhoehe       = 700
    ProgrammX_Position  = 0
    ProgrammY_Position  = 0

    root.geometry('%dx%d+%d+%d' % (Programmbreite,
                               Programmhoehe,
                               ProgrammX_Position,
                               ProgrammY_Position))
    Programmname = 'Notationsprogramm'
    root.title(Programmname)

    # Position des Darstellungsfensters
    xpos    = 80
    ypos    = 80
    width   = 915
    height  = 615   
    w = Darstellungsfenster(root, dict(width=width,height=height))
    w.pack(expand=True, fill= "both")
    
    w.draw_takt()
    #w.draw_takt()
    #w.draw_takt()

    root.mainloop()

if __name__=="__main__":
    hauptprogramm()
Gruß wuf :wink:
Take it easy Mates!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

daemonTutorials hat geschrieben:Fast genauso malt man mit Canvas, nur eben nicht live sondern statisch!
Das ist Quark. Abgesehen davon, dass "live" und "statisch" hier keine treffende Wortwahl ist, kannst du sehr wohl auf ein Canvas auch interaktiv zeichnen:

Code: Alles auswählen

Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Tkinter as tk
>>> c = tk.Canvas()
>>> c.pack()
>>> c.create_line(0,0,300,200)
1
>>> c.create_line(300,200,0,200,fill="red")
2
Im übrigen ist das in diesem Thread gar nicht das Problem. Es geht vielmehr darum, ein Canvas in eine GUI sauber zu integrieren und den OP nebenbei davon zu überzeugen, dass er place() möglichst begraben sollte.
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Was soll man bei so einem Titel und so einem Anfangsbeitrag auch anders denken?

Meine Auffassungsgabe war an diesem Tag wohl ausgeschaltet, sry!
LG Maik
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

Weiteres kleines Problem. ;)
wie kann ich bei einer Liste von x,y Positionen

Code: Alles auswählen

self.clef= [(38,164), (40,170), (47,165), ( 37, 131), (44, 126), (45,136),
                     (32,150), (40,161), (50,156), (47,144), ( 37,149), (40,154)]
jeweils alle y Positionen um einem bestimmten Wet verändern?
Am schluss brauche ich wieder eine Liste...

Mein Programm bisher:
bei der Funktion draw_key sollte bei jeder neuen Zeile auch der Notenschlüssel in der neuen Zeile gezeichnet werden und nicht immer in der 1. Zeile
http://www.python-forum.de/pastebin.php?mode=view&s=212
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

questlove hat geschrieben:wie kann ich bei einer Liste von x,y Positionen
jeweils alle y Positionen um einem bestimmten Wet verändern?
Am schluss brauche ich wieder eine Liste...
for, map, oder list comprehesion
Das Leben ist wie ein Tennisball.
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

danke :)
questlove
User
Beiträge: 65
Registriert: Dienstag 15. Februar 2011, 16:11

so..
jetzt hab ich ein etwas größeres Problemchen..
Und zwar ;):
Ich bin ja noch ein Anfänger mit Python. Und ich hab jetzt einfach mal für mein "Projekt" drauflosgeschrieben, ohne dass ich das geändert habe was mir Xynon1 geraten hat:
Xynon1 hat geschrieben:Ich werde mal mein Programm dokumentieren und dann nochmal hochladen.

Zu deinem Problem:
- Arbeite nicht mit "place"-Methoden das ist sowas von altmodisch und ohne Designer reine Folter. Nimm am besten nur "pack"-Methoden und sie mal wie weit du kommst.
- Lass niemals das Widget sich selbst auf sein Eltern-Widget packen. So kann man als Nutzer von diesem Widget nicht mehr den Geometry-Manager bestimmen.
- Man kann es nicht oft genug sagen: Halte dich an PEP8.

So könnte man es z.B. machen:

Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk

class DarstellungsFenster(tk.Canvas):

     def __init__(self, master, cnf={}):
        # bedenke das border, relief und background so Standard sind
        # und deine Werte die du in 'cnf' übergibst eventuell überschreibst
        tk.Canvas.__init__(self, master, cnf, border=4, relief='ridge',
                           background='white')


def hauptprogramm():
    wurzel = tk.Tk()
    breite, hoehe = 800, 600
    x, y = 0, 0
    wurzel.geometry('%dx%d+%d+%d' % (breite, hoehe, x, y))
    wurzel.title('Notationsprogramm')
    darstellungsfenster = DarstellungsFenster(wurzel, dict(width=715, height=515))
    # expand sorgt dafuer das dein Widget immer genauso gross ist wie dein 
    # wurzel-Fenster
    darstellungsfenster.pack(expand=True, fill="both")

# Sonst wird der Code auch bei einem Import aufgerufen 
if __name__ == "__main__":
    hauptprogramm()
jetzt will ich statt mit Linien und Kreise in Canvas, den Notenschlüssel und die Noten durch ein gif einfach einzufügen, was wesentlich einfacher und schöner funtioniert.
Aber das eingefügte Bild per

Code: Alles auswählen

        Violinkey = tk.PhotoImage(file='vns_50.gif')
        self.create_image(130,130,image=Violinkey)
sieht man nur nur wenn ich dann noch
self.pack(dict(width=400,hight=400))
wobei self.pack() nicht ausreicht, was ich nicht verstehe.
Ich dachte es liegt nur an dem pack.

Meine Frage: Ich hab nirgens gefunden wie ich das anders einfügen kann. Was muss ich jetzt umschreiben, bzw wie? Oder gibt es eine Lösung um dem umschreiben auf die pack-Funktionen zu entkommen? ;)

(Ausschnitt aus GUI, class Darstellungsfenster,def__init__)
ich habs nur mal ins __init__ getan um zu sehen obs überhaupt geht ;)

mein code besteht aus 2 modulen:
1. Logik
2. GUI

LG
questlove
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Bei den "create_"*-Methoden gibt es keinen Geometry-Manager (also weder pack, grid oder place), hier hast du nur die Möglichkeit mit Punkten zuarbeiten. d.h. für ein System in welchem du Bilder aneinaderreihen möchtest musst du dir selbst ein paar funktionen schreiben um das zu verwalten.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten