Binary Clock mit tkinter

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
sepplx123
User
Beiträge: 13
Registriert: Montag 14. Dezember 2015, 04:13

Hallo liebe Leute,

ich versuche diese Binäruhr auf der Webseite http://www.bartelmus.org/binar-uhr/#more-136 nachzubauen.

Es gibt da aber noch ein Verständnisproblem, wie genau die Animation funktioniert.

Ich habe es so verstanden, dass nach genau 24h der äußerste Zeiger genau 1 Umdrehung vollzogen hat.
Die anderen Zeiger dann entsprechend nachfolgender Tabelle:
Also Zeiger 5 1 Umdrehung, Zeiger 4 macht 16, Zeiger 3 128, Zeiger 2 512 und Zeiger 1 1024.
24h entspricht 24*60*60s = 86400s
-----------------------------------------------------------------------------
Zeiger 1 2 * 1/2 2 8 64 1024 * 360° =>> 1024*360°/86400s ==> 4,2666 °/s
Zeiger 2 4 * 1/4 1 4 32 512 * 360° =>> 512*360°/86400s ==> 2,1333 °/s
Zeiger 3 8 * 1/8 1 8 128 * 360° =>>
Zeiger 4 16 * 1/16 1 16 * 360° =>>
Zeiger 5 32 * 1/32 1 * 360° =>>

Andere Vorschläge?


Ich habe das mal grafisch per tkinter programmiert in python 2.7.10.
Es ist nicht sonderlich schön, aber ich versuche mich zu verbessern und habe zum ersten Mal das Konzept der Klassen einigermaßen kapiert und umgesetzt. Ebenfalls wie man die mainloop in tkinter nutzen kann damit es funktioniert. Aber ob das auch alles so richtig ist, weiss ich bisher noch nicht und bin daher auf Feedback gespannt.

Die Animation hat bisher noch keine Zeitbasis, deshalb auch der Geschwindigkeitsregler um mal zu sehen wie es prinzipiell funktioniert.
Der Mittlere Punkt (im Programm Pulsar genannt) soll die Mikro-Sekunden darstellen. Dessen Geschwindigkeit passt bereits und kann nicht verändert werden. Immer wenn eine Sekunde voll ist, ist der Kreis maximal groß und fängt dann wieder bei 0 an.

Vorschläge zur besseren Strukturierung sind immer Willkommen :wink: .
Es ist noch etwas wenig Kommentar vorhanden und die Zeilenlängen übersteigen meistens die im Styleguide vorgeschriebene Länge.
Das wird noch gefixt. Mir geht es eher um Übersichtlichkeit und wie man denn richtiger weise eine saubere Struktur rein bekommt.

Code: Alles auswählen

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

from __future__ import (absolute_import, division,print_function, unicode_literals)

import time
from math import *
import Tkinter as tk
from datetime import datetime


class Uhrzeit(): 
    def __init__(self):
        #print(self.anzeigen(), 'class init Uhrzeit done')
        pass

    def anzeigen(self):
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        self.micros = int(self.dt.microsecond/1000)
        #self.format = str(self.hours)+':'+str(self.minutes)+':'+str(self.seconds)+':'+str(self.micros)
        #print("{0:02d}:{1:02d}:{2:02d}:{3:03d}".format(self.hours,self.minutes,self.seconds,self.micros))
        return ("{0:02d}:{1:02d}:{2:02d}:{3:03d}".format(self.hours,self.minutes,self.seconds,self.micros))

    def values(self):
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        self.micros = int(self.dt.microsecond/1000)
        return self.hours,self.minutes,self.seconds,self.micros

    def hms_anzeigen(self):
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        #return time.strftime("%H:%M:%S")
        return ("{0:02d}:{1:02d}:{2:02d}".format(self.hours,self.minutes,self.seconds))
    
    def get_micros(self):
        self.dt = datetime.now()
        self.micros = int(self.dt.microsecond/1000)       
        return self.micros
    
class GUI():
    def __init__(self, master):
        self.master = master
        self.width = 800
        self.height = 800       
        self.uhr = Uhrzeit()
        self.cycle_time = 1    # time in ms for one program cycle

        self.color_bg = '#ffffff'
        self.color_movers =  '#00bfff' #'#4169e1' #
        self.color_markings = '#87cefa' #'#6495ed'
        marking_angle = []
        marking_items = [] 
        self.counter = 0
        
        self.start_stop = tk.BooleanVar()
        self.start_stop.set(False)
        self.start_stop.trace("w", self.callback)

        self.strv_zeitangabe = tk.StringVar()            
        self.strv_uhrzeit = tk.StringVar()
        self.strv_uhrzeit.trace("w", self.callback)
                  
        # Frames             
        self.fr1 = tk.Frame(self.master, width=100, height=50, background='white')
        self.fr2 = tk.Frame(self.master, width=100, height=50, background='grey')
        self.fr3 = tk.Frame(self.master, width=100, height=20, background='white')
        self.fr1.pack(expand=1,fill='both')
        self.fr2.pack(expand=1,fill='both', anchor='center')
        self.fr3.pack(expand=1,fill='both')

        # Buttons
        self.bStart = tk.Button(self.fr1,width=10, height=1, bg='red', text = "Start", command = self.startbutton )
        self.bStart.pack(side='left', padx=10, anchor='center')
        self.bReset = tk.Button(self.fr1,width=10, height=1, bg='red', text = "Reset", command = self.reset)
        self.bReset.pack(side='left', padx=10, anchor='center')
        self.bQuit = tk.Button(self.fr1,width=10, height=1, bg='red', text = "Quit", command = self.exitprogramm)
        self.bQuit.pack(side='right', padx=10, anchor='center')

        # Scales
        self.speed_label = tk.Label(self.fr2, bg='red', highlightthickness=1, height=2, text='Speed Adjustment')
        self.speed_label.pack(side='left', padx=0,anchor='n')
        self.speed_slider = tk.Scale(self.fr2, bg='red',length=300, troughcolor='black', highlightbackground='blue', highlightthickness=0, from_=1, to=100, orient='vertical')
        self.speed_slider.pack(side='left', padx=0,pady=0, anchor='n')
        self.speed_slider.set(10)
        
        # Canvas         
        self.canvas = tk.Canvas(self.fr2, width = self.width, height = self.height, highlightthickness=0, background=self.color_bg)
        self.canvas.pack(expand=1, anchor='center')

        # Labels
        self.label_uhrzeit = tk.Label(self.fr1, textvariable=self.strv_zeitangabe,bg='grey',fg='red',width=50, height=1)
        self.label_uhrzeit.config(font=("Arial", 20))
        self.label_uhrzeit.pack(side='left',anchor='center')

        # Text Items
        

        # Layout Design:   Lines ==> Movers ==> Kreise ==> Pulsar
        marking_angle = [360-360,360/2]
        for item in marking_angle:
            marking_items.append(Markings(self.canvas,370,item,70,item,fill=self.color_markings,width=2)) # 0, 180°
        marking_angle = [360/4,360-360/4]       
        for item in marking_angle:
            marking_items.append(Markings(self.canvas,370,item,70+60,item,fill=self.color_markings,width=2)) # 90, 270°
        marking_angle = [360/8*1,360/8*3,360/8*5,360/8*7]       
        for item in marking_angle:
            marking_items.append(Markings(self.canvas,370,item,70+60*2,item,fill=self.color_markings,width=2)) #
        marking_angle = [360/16*1,360/16*3,360/16*5,360/16*7,360/16*9,360/16*11,360/16*13,360/16*15]       
        for item in marking_angle:
            marking_items.append(Markings(self.canvas,370,item,70+60*3,item,fill=self.color_markings,width=2)) #
        marking_angle = [360/32*1,360/32*3,360/32*5,360/32*7,360/32*9,360/32*11,360/32*13,360/32*15,360/32*17,360/32*19,360/32*21,
                         360/32*23,360/32*25,360/32*27,360/32*29,360/32*31]       
        for item in marking_angle:
            marking_items.append(Markings(self.canvas,370,item,70+60*4,item,fill=self.color_markings,width=2)) #
        
        self.line1 = Lines(self.canvas,400,400,340,outline=self.color_markings,width=2)                        # Linien
        self.mover1 = Movers(self.canvas, x=400, y=400, r=360, start=90, extent=(360/32), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.mover11 = Kreise(self.canvas, x=400, y=400, r=320, start=90, extent=(360/32)+2, outline=self.color_bg, fill=self.color_bg ,width=2)

        self.line2 = Lines(self.canvas,400,400,280,outline=self.color_markings,width=2)
        self.mover2 = Movers(self.canvas, x=400, y=400, r=300, start=90, extent=(360/16), outline=self.color_movers, fill=self.color_movers,width=1)
        self.mover22 = Kreise(self.canvas, x=400, y=400, r=260, start=90, extent=(360/16)+2, outline=self.color_bg, fill=self.color_bg ,width=2)
                      
        self.line3 = Lines(self.canvas,400,400,220,outline=self.color_markings,width=2)
        self.mover3 = Movers(self.canvas, x=400, y=400, r=240, start=90, extent=(360/8), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.mover33 = Kreise(self.canvas, x=400, y=400, r=200, start=90, extent=(360/8)+2, outline=self.color_bg, fill=self.color_bg ,width=2)
        
        self.line4 = Lines(self.canvas,400,400,160,outline=self.color_markings,width=2)
        self.mover4 = Movers(self.canvas, x=400, y=400, r=180, start=90, extent=(360/4), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.mover44 = Kreise(self.canvas, x=400, y=400, r=140, start=90, extent=(360/4)+2, outline=self.color_bg, fill=self.color_bg, width=2)
                             
        self.line5 = Lines(self.canvas,400,400,100,outline=self.color_markings,width=2)
        self.mover5 = Movers(self.canvas, x=400, y=400, r=120, start=90, extent=(360/2), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.mover55 = Kreise(self.canvas, x=400, y=400, r=80, start=90, extent=(360/2)+2, outline=self.color_bg, fill=self.color_bg ,width=2)

        self.pulsar = Pulsar(self.canvas,400,400,60,outline=self.color_movers,fill=self.color_movers,width=1)        # Pulsar
        #self.canvas.lift(self.movers[item])
        #self.canvas.lift(self.circles[item])
        
        #print(self.uhr.anzeigen(), 'class init GUI done')
        self.update_time()              # Mainloop
        ###### Init finished #####


    def callback(self, *event):
        self.speed = self.speed_slider.get()
        if 0 < self.speed and self.speed <= 10:
            self.speed = self.speed / 10
        elif 10 < self.speed and self.speed <= 20:
            self.speed = self.speed / 5
        elif 20 < self.speed and self.speed <= 30:
            self.speed = self.speed / 2.5
        elif 30 < self.speed and self.speed <= 40:
            self.speed = self.speed / 2
        elif 40 < self.speed and self.speed <= 50:
            self.speed = self.speed / 1.5
            
        self.counter += self.speed

        if self.start_stop.get():     
            if  self.counter >= 100:
                self.counter = 0
                self.animation_mover()
            self.animation_pulsar()

    def update_time(self):
        self.strv_uhrzeit.set(self.uhr.anzeigen())                          # Loop for animation
        self.strv_zeitangabe.set(self.uhr.hms_anzeigen())                   # Show the actual time on top of the window
                                                                            # schedule timer to call myself after a defined time in ms
        self.master.after(self.cycle_time, self.update_time)                # Mainloop

    def exitprogramm(self,*event):
        self.master.destroy()

    def startbutton(self):
        if not self.start_stop.get():
            self.start_stop.set(True)
            self.bStart.config(text='Stop',bg='green')
        else:
            self.start_stop.set(False)
            self.bStart.config(text='Start',bg='red')            

    def animation_mover(self):
        '''
        Mover Rotation Increments per 1 Second:
        
        reference:      http://www.bartelmus.org/binar-uhr/#more-136

        In dieser Implementierung existieren 5 Zeiger. 
        Die Zahlensysteme für die Zeiger sind dabei: (2, 4, 8, 16, 32).

        Die Größen der Zeiger (in Bezug zu einen Gesamtkreis) sind von innen nach aussen: (1/2, 1/4, 1/8, 1/16, 1/32). 
        Der innerste Kreis dient nur als Stilelement und hat keine Funktion.

        Die Bedingung für eine vollständige Umdrehung eines Zeigers ist die Umdrehung des 
        nächstinneren Zeigers um genau den Umkehrwert seiner Größe im Bezug auf einen Kreis.

        Z.B.: der dritte Zeiger vollzieht genau dann eine vollständige Umdrehung, 
        wenn der zweite Zeiger 4 Umdrehungen vollzogen hat.

        Wenn 24 Stunden vergangen sind, sind alle Teilkreise bis auf den kleinsten um den Umkehrwert ihrer Größe gewandert. 
        Der kleinste (äußerste) Kreis hat dann eine Umdrehung erreicht.

        24h entspricht 24*60*60s = 86400s
        -----------------------------------------------------------------------------
         2 * 1/2 	2	8	64	1024	* 360° =>> 1024*360°/86400s ==> 4,2666 °/s
         4 * 1/4	1	4	32	512	* 360° =>>  512*360°/86400s ==> 2,1333 °/s
         8 * 1/8		1	8	128	* 360° =>>  128*360°/86400s ==> 0,5333 °/s
        16 * 1/16			1	16	* 360° =>>   16*360°/86400s ==> 
        32 * 1/32				1	* 360° =>>    1*360°/86400s ==>
        '''
        self.mover1_inc = 360/86400*1           # The Incredements each mover should move per one cycle
        self.mover2_inc = 360/86400*16
        self.mover3_inc = 360/86400*128
        self.mover4_inc = 360/86400*512
        self.mover5_inc = 360/86400*1024
        
        self.mover5.rotate(self.mover5_inc)     # The Movers
        self.mover4.rotate(self.mover4_inc)
        self.mover3.rotate(self.mover3_inc)
        self.mover2.rotate(self.mover2_inc)
        self.mover1.rotate(self.mover1_inc)
        
        self.mover55.rotate(self.mover5_inc)    # The cover circles to hide the arcs
        self.mover44.rotate(self.mover4_inc)
        self.mover33.rotate(self.mover3_inc)
        self.mover22.rotate(self.mover2_inc)
        self.mover11.rotate(self.mover1_inc)

    def animation_pulsar(self):
        self.pulsar.puls()        

    def reset(self):
        if self.start_stop.get():
            self.start_stop.set(False)
            self.bStart.config(text='Start',bg='red')
        
        self.mover5.reset()
        self.mover4.reset()
        self.mover3.reset()
        self.mover2.reset()
        self.mover1.reset()
        self.pulsar.reset()

        self.mover55.reset()
        self.mover44.reset()
        self.mover33.reset()
        self.mover22.reset()
        self.mover11.reset()
        

class Markings():
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,radius_1,angle_1,radius_2,angle_2,fill,width):
        self.canvas = canvas
        self.x1, self.y1 = self.conv_pol_to_xy_coords(radius_1,angle_1)
        self.x2, self.y2 = self.conv_pol_to_xy_coords(radius_2,angle_2)     
        item = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2, fill=fill, width=width, tags="marking_line")
        return item

    def conv_pol_to_xy_coords(self,radius,angle,offsetx=400,offsety=400):   # Funktion konvertiert Polar-Koords in XY-Koords
        self.direction = 1
        self.angle_offset = 90
        self.angle = self.angle_offset - angle
        coord_x = offsetx+radius*round(cos(radians(self.direction*self.angle)),3)      #berechnete Soll-Position am Kreis in X mit 3 Dezimalstellen
        coord_y = offsety+radius*round(sin(radians(self.direction*(-self.angle))),3)     #berechnete Soll-Position am Kreis in Y mit 3 Dezimalstellen
        return coord_x, coord_y


class Lines():
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,width):
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,outline=outline,width=width, tags='outline')
        return item

class Kreise():
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lower(self.id)
        self.angleOffset = -90 + 1
        self.direction= -1
        self.angle = 0
        self.radius = 200
        self.line_length = 0

    def create(self,canvas,x,y,r,start,extent,outline,fill,width):
        self.canvas = canvas
        item = canvas.create_arc(x-r,y-r,x+r,y+r,start=start,extent=extent,outline=outline,fill=fill,width=width, tags='circle')
        return item
     
    def rotate(self,increment):
        #x1, y1, x2, y2 = self.canvas.bbox(self.id)
        self.increment = increment
          
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))       # Changes the start angle

        if self.angle >= 360.0:
            self.angle -= 360.0
          
        self.angle += self.increment
               

    def output_angle(self):
        return self.angle

    def reset(self):
        self.angle = 0
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))


class Movers():
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lower(self.id)
        self.angleOffset = -90
        self.direction= -1
        self.angle = 0


    def create(self,canvas,x,y,r,start,extent,outline,fill,width):
        self.canvas = canvas
        item = canvas.create_arc(x-r,y-r,x+r,y+r,start=start,extent=extent,outline=outline,fill=fill,width=width, tags='mover')
        return item
     
    def rotate(self,increment):
        #x1, y1, x2, y2 = self.canvas.bbox(self.id)
        self.increment = increment
          
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))       # Changes the start angle

        if self.angle >= 360.0:
            self.angle -= 360.0
        self.angle += self.increment
               
    def reset(self):
        self.angle = 0
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))

        
class Pulsar():
    def __init__(self, canvas, *args, **kwargs):
        self.uhr = Uhrzeit()
        
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,fill,width):
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,outline=outline, fill=fill ,width=width,tags='pulsar')
        return item
        
    def puls(self):
        self.micros = self.uhr.get_micros()
        micros_len_pointer = self.micros/1000 * 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer, 400 + micros_len_pointer, 400 + micros_len_pointer)


    def reset(self):
        micros_len_pointer = 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer, 400 + micros_len_pointer, 400 + micros_len_pointer)     


def main():
    root = tk.Tk()
    ui = GUI(root)
    root.mainloop()


if __name__ == '__main__':
    main()
BlackJack

@sepplx123: Die Zeit für eine komplette Anzeige würde ich nur einmal abfragen, also nur *ein* Exemplar von `Uhrzeit` erstellen und die Zeit die damit ermittelt wird dann für alle Anzeigeteile verwenden und nicht jedes seine eigene Zeit ermitteln lassen die dann alle leicht voneinander abweichen.

Die Klasse Uhrzeit ist keine wirkliche Klasse weil die `__init__()` nichts tut (man hätte die Methode auch weglassen können) und die Methoden als Funktionen geschrieben werden könnten. Eine Methode die ihre Ergebnisse als Attribute setzt *und* zurückgibt ist ein „code smell“. Entweder man will das Ergebnis zurückgeben oder man will den Zustand des Objekts durch das Ergebnis setzen. Beides zusammen ist selten sinnvoll. Ich denke Du bist da ein bisschen zu sehr dem ”Klassenwahn” verfallen. Es ist nichts falsch daran Funktionen zu verwenden wenn eine Klasse keinen Mehrwert bringt. Das macht ein Programm nicht mehr objektorientiert. Funktionen sind in Python letztlich ja auch Objekte.

Ich sehe in dem Code nirgends wo Du die aktuelle Uhrzeit in Zeigerstellungen umwandelst? *Das* wäre doch schon mal eine sehr zentrale *Funktion*, eine die Beispielsweise ein `datetime.time`-Objekt und die Anzahl der Ringe entgegen nimmt und eine Liste mit den logischen Zeigerpositionen liefert, entweder als Gleitkommazahlen oder als Werte zwischen 0 und 2**n mit n als der Anzahl der Ringe.
sepplx123
User
Beiträge: 13
Registriert: Montag 14. Dezember 2015, 04:13

Hallo Blackjack,

vielen Dank für dein Feedback.
Ich habe mal deine Vorschläge versucht positiv umzusetzen und habe eine neuere Version hochgeladen.

BlackJack hat geschrieben:@sepplx123: Die Zeit für eine komplette Anzeige würde ich nur einmal abfragen, also nur *ein* Exemplar von `Uhrzeit` erstellen und die Zeit die damit ermittelt wird dann für alle Anzeigeteile verwenden und nicht jedes seine eigene Zeit ermitteln lassen die dann alle leicht voneinander abweichen.
Danke. Das war ein wichtiger Punkt, den ich eliminiert habe.
BlackJack hat geschrieben: Die Klasse Uhrzeit ist keine wirkliche Klasse weil die `__init__()` nichts tut (man hätte die Methode auch weglassen können) und die Methoden als Funktionen geschrieben werden könnten. Eine Methode die ihre Ergebnisse als Attribute setzt *und* zurückgibt ist ein „code smell“. Entweder man will das Ergebnis zurückgeben oder man will den Zustand des Objekts durch das Ergebnis setzen. Beides zusammen ist selten sinnvoll. Ich denke Du bist da ein bisschen zu sehr dem ”Klassenwahn” verfallen. Es ist nichts falsch daran Funktionen zu verwenden wenn eine Klasse keinen Mehrwert bringt. Das macht ein Programm nicht mehr objektorientiert. Funktionen sind in Python letztlich ja auch Objekte.
Ja da hast du durchaus Recht. Ich wollte einfach alles was mit der Uhrzeit und Berechnung der Zeit und Rückgabewerte aus der Klasse GUI entkoppeln. Das war in dem Fall nicht sehr erfolgreich und sogar aufwendiger, als wenn man das einfach per Funktionsaufruf gestaltet. Aber jetzt ist sie noch etwas gewachsen, vielleicht passt es jetzt besser.
BlackJack hat geschrieben: Ich sehe in dem Code nirgends wo Du die aktuelle Uhrzeit in Zeigerstellungen umwandelst? *Das* wäre doch schon mal eine sehr zentrale *Funktion*, eine die Beispielsweise ein `datetime.time`-Objekt und die Anzahl der Ringe entgegen nimmt und eine Liste mit den logischen Zeigerpositionen liefert, entweder als Gleitkommazahlen oder als Werte zwischen 0 und 2**n mit n als der Anzahl der Ringe.
Ja okay kann man machen.
Mein Weg war nun, da ja jedes Anzeigeobjekt (Mover genannt) separat erstellt wurde, kann man hier eine Funktion für die Positionsberechnung erstellen. Ich frage von der Kasse Uhrzeit die aktuelle Zeit in Sekunden ab und greife dann mit diesen Wert und einer Kennung, welche "Mover" gemeint ist, mit der Funktion *show_time* auf die aktuelle Position zu.
Es gibt eine Liste

Code: Alles auswählen

self.movers = [self.mover1,self.mover2,self.mover3,self.mover4,self.mover5]

,worin die 5 Elemente stehen. Jedes Element wird in einer Schleife aufgerufen und die aktuelle uhrzeit in Sekunden samt der Kennung(item+1) weitergereicht.

Code: Alles auswählen

if self.time_anim_status.get():                                     
            for item in range(len(self.movers)):                
                self.hiders[item].show_time(self.time_in_s,int(item+1))
                self.movers[item].show_time(self.time_in_s,int(item+1))
Die Positionsbestimmung und Zeichnung findet dann in der Klasse *Movers* bei er Funktion *show_time* statt.

Code: Alles auswählen

calc_position = self.direction * (self.angleOffset + (360 / 86400 * self.value * self.time_in_s)) # calculates the position according to the actual time
        self.canvas.itemconfig(self.id, start=calc_position)        # Changes the start angle according to the given value
Das Programm hat nun eine Simulationsdarstellung und eine die die aktuelle Uhrzeit interpretiert und entsprechend darstellt.

Was mir gar nicht gefällt ist, dass man mit tk keine Ringe erstellen kann, sondern nur Vollkreise oder eben *Arcs*. Gibt es hierfür eine Möglichkeit der Optimierung oder ein anderes Tool womit das besser möglich ist?
Laut meinem Verständnis soll die Klasse GUi wirklich nur Anzeigeelemente erzeugen und positionieren. Aktuell ist aber auch der Event-handler und noch so manch andere Funktion darin integriert, die da eigentlich fehl am Platz ist. Ach ja, der Mainloop ist ja auch im GUI. Könnte man das irgendwie entkoppeln oder besser lösen?

Code: Alles auswählen

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

from __future__ import (absolute_import, division,print_function, unicode_literals)
from math import (sin, cos, pi)
import Tkinter as tk
from datetime import datetime


class Uhrzeit(): 
    '''  Creates the actual time and the necessary values for other functions. '''
    def __init__(self):
        self.dt = datetime.now()
        self.hours = 0
        self.minutes = 0
        self.seconds = 0
        self.micros = 0
        self.time_s = 0
        self.time_format = 0
        #print("{0:02d}:{1:02d}:{2:02d}:{3:03d}".format(self.hours,self.minutes,self.seconds,self.micros))

    def anzeigen(self):
        '''  Returns the actual time in a proper format hh:mm:ss:micros '''
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        self.micros = int(self.dt.microsecond/1000)
        #print("{0:02d}:{1:02d}:{2:02d}:{3:03d}".format(self.hours,self.minutes,self.seconds,self.micros))
        return ("{0:02d}:{1:02d}:{2:02d}:{3:03d}".format(self.hours,self.minutes,self.seconds,self.micros))

    def hms_anzeigen(self):
        '''  Returns the actual time in a proper format hh:mm:ss '''
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        #return time.strftime("%H:%M:%S")
        return ("{0:02d}:{1:02d}:{2:02d}".format(self.hours,self.minutes,self.seconds))

    def values(self):
        '''  Returns the values of the actual time '''
        self.dt = datetime.now()
        self.hours = self.dt.hour
        self.minutes = self.dt.minute
        self.seconds = self.dt.second
        self.micros = int(self.dt.microsecond/1000)
        return self.hours,self.minutes,self.seconds,self.micros
    
    def get_micros(self):
        '''  Calculates the microseconds of the actual time '''
        self.dt = datetime.now()
        self.micros = int(self.dt.microsecond/1000)       
        return self.micros

    def time_in_s(self):
        '''  Transforms the actual time in seconds '''
        self.hours,self.minutes,self.seconds,self.micros = self.values()
        self.time_s = (self.hours*3600) + (self.minutes*60) + self.seconds
        return self.time_s
    
    def time_from_s(self,time_in_s):
        '''  Transforms the given time in seconds back to the format hh:mm:ss '''
        self.time_s = time_in_s
        hours, rest = divmod(self.time_s, 3600)
        minutes, seconds = divmod(rest, 60)
        return ("{0:02d}:{1:02d}:{2:02d}".format(hours,minutes,seconds))
        
    
class GUI():
    '''  Creates and draws all ui elements  in the window. '''
    def __init__(self, master):
        self.master = master
        self.width = 800
        self.height = 800       
        self.uhr = Uhrzeit()
        self.cycle_time = 1                 # time in ms for one program cycle

        self.color_bg =         '#ffffff'
        self.color_movers =     '#00bfff'   #'#4169e1' #
        self.color_m_Circle_Outlines =   '#87cefa'   #'#6495ed' #
        marking_angle = []
        marking_items = [] 
        self.counter = 0
        self.micros = 0
        self.hms_last_cycle = 0
        self.movers = []
        self.hiders = []
        self.simulation_counter = 0
        
        self.start_stop = tk.BooleanVar()
        self.start_stop.set(False)
        self.start_stop.trace("w", self.callback)

        self.strv_zeitangabe = tk.StringVar()
        
        self.strv_uhrzeit = tk.StringVar()
        self.strv_uhrzeit.trace("w", self.callback) # calls the function for animate the general movement wihtout any time correspandance
        
        self.sim_uhrzeit = tk.StringVar()
        self.sim_uhrzeit.set('00:00:00')
        
        self.time_anim_status = tk.BooleanVar()
        self.time_anim_status.set(False)

        # Frames             
        self.fr1 = tk.Frame(self.master, width=100, height=50, background='grey')
        self.fr2 = tk.Frame(self.master, width=100, height=50, background='grey')
        self.fr3 = tk.Frame(self.master, width=100, height=20, background='grey')
        self.fr1.pack(expand=1,fill='both')
        self.fr2.pack(expand=1,fill='both', anchor='center')
        self.fr3.pack(expand=1,fill='both')

        # Buttons
        self.bStart = tk.Button(self.fr2,width=0, height=1, bg='red',bd=5, text = "Simulation Start", command = self.startbutton )
        self.bStart.grid(row=2,column=0,padx=10, pady=10, sticky='nw')
        self.bReset = tk.Button(self.fr2,width=0, height=1, bg='red',bd=5, text = "Simulation Reset", command = self.reset)
        self.bReset.grid(row=2,column=1,padx=10, pady=10, sticky='nw')
        self.bQuit = tk.Button(self.fr1,padx=0, pady=5,width=10, height=1,bd=5, bg='red', text = "Quit", command = self.exit_program)
        self.bQuit.pack(side='right', padx=10, anchor='center')
        
        self.bAnimTime = tk.Button(self.fr2,width=30,padx=0, pady=5, height=1,bd=5, bg='blue', text = "Time Animation Start", command = self.time_animation_start_stop)      
        self.bAnimTime.grid(row=0,column=0,columnspan=2,padx=10, pady=5, sticky='nw')


        # Scales
        self.speed_label = tk.Label(self.fr2, bg='red', highlightthickness=1, height=2, text='Speed Adjustment')
        self.speed_label.grid(row=3,column=0,ipadx=0,pady=0,sticky='ne')
        self.speed_slider = tk.Scale(self.fr2, bg='red',length=300, troughcolor='black', highlightbackground='blue', highlightthickness=0, from_=1, to=100, orient='vertical')
        self.speed_slider.set(10)
        self.speed_slider.grid(row=3,column=1,ipadx=0,pady=0,sticky='n')
        
        # Canvas         
        self.canvas = tk.Canvas(self.fr2, width = self.width, height = self.height, highlightthickness=0, background=self.color_bg)
        self.canvas.grid(row=0,column=2,rowspan=10)

        # Labels
        self.label_uhrzeit = tk.Label(self.fr1, textvariable=self.strv_zeitangabe,bg='grey',fg='red',width=50, height=1,font=("Arial", 20))
        self.label_uhrzeit.pack(side='left',padx=0,ipadx=0, anchor='center')
        
        self.label_Simulation = tk.Label(self.fr2, text='Simulated time',bg='white',fg='red',width=20, height=2,font=("Arial", 15))
        self.label_Simulation.grid(row=1,column=0,columnspan=2, ipadx=0,ipady=10,sticky='n')       
        self.label_sim_uhrzeit = tk.Label(self.fr2, textvariable=self.sim_uhrzeit,bg='white',fg='red',width=20, height=0,font=("Arial", 15))
        self.label_sim_uhrzeit.grid(row=1,column=0,columnspan=2,ipadx=0,ipady=10,pady=0,sticky='s')
        

        # Text Items
        

        # Layout Design:   Circle_Outlines ==> Movers ==> Hiders ==> Pulsar
        marking_angle = [360-360,360/2]
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70,item,fill=self.color_m_Circle_Outlines,width=2)) # 0, 180°
        marking_angle = [360/4,360-360/4]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60,item,fill=self.color_m_Circle_Outlines,width=2)) # 90, 270°
        marking_angle = [360/8*1,360/8*3,360/8*5,360/8*7]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*2,item,fill=self.color_m_Circle_Outlines,width=2)) #
        marking_angle = [360/16*1,360/16*3,360/16*5,360/16*7,360/16*9,360/16*11,360/16*13,360/16*15]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*3,item,fill=self.color_m_Circle_Outlines,width=2)) #
        marking_angle = [360/32*1,360/32*3,360/32*5,360/32*7,360/32*9,360/32*11,360/32*13,360/32*15,360/32*17,360/32*19,360/32*21,
                         360/32*23,360/32*25,360/32*27,360/32*29,360/32*31]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*4,item,fill=self.color_m_Circle_Outlines,width=2)) #
        
        self.line1 = Circle_Outlines(self.canvas,400,400,340,outline=self.color_m_Circle_Outlines,width=2)                        # Linien
        self.mover1 = Movers(self.canvas, x=400, y=400, r=360, start=90, extent=(360/32), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider1 = Movers(self.canvas, x=400, y=400, r=320, start=90, extent=(360/32)+2, outline=self.color_bg, fill=self.color_bg ,width=3)

        self.line2 = Circle_Outlines(self.canvas,400,400,280,outline=self.color_m_Circle_Outlines,width=2)
        self.mover2 = Movers(self.canvas, x=400, y=400, r=300, start=90, extent=(360/16), outline=self.color_movers, fill=self.color_movers,width=1)
        self.hider2 = Movers(self.canvas, x=400, y=400, r=260, start=90, extent=(360/16)+2, outline=self.color_bg, fill=self.color_bg ,width=3)
                      
        self.line3 = Circle_Outlines(self.canvas,400,400,220,outline=self.color_m_Circle_Outlines,width=2)
        self.mover3 = Movers(self.canvas, x=400, y=400, r=240, start=90, extent=(360/8), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider3 = Movers(self.canvas, x=400, y=400, r=200, start=90, extent=(360/8)+2, outline=self.color_bg, fill=self.color_bg ,width=3)
        
        self.line4 = Circle_Outlines(self.canvas,400,400,160,outline=self.color_m_Circle_Outlines,width=2)
        self.mover4 = Movers(self.canvas, x=400, y=400, r=180, start=90, extent=(360/4), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider4 = Movers(self.canvas, x=400, y=400, r=140, start=90, extent=(360/4)+2, outline=self.color_bg, fill=self.color_bg, width=3)
                             
        self.line5 = Circle_Outlines(self.canvas,400,400,100,outline=self.color_m_Circle_Outlines,width=2)
        self.mover5 = Movers(self.canvas, x=400, y=400, r=120, start=90, extent=(360/2), outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider5 = Movers(self.canvas, x=400, y=400, r=80, start=90, extent=(360/2)+2, outline=self.color_bg, fill=self.color_bg ,width=3)

        self.pulsar = Pulsar(self.canvas,400,400,60,outline=self.color_movers,fill=self.color_movers,width=1)        # Pulsar
        #self.canvas.lift(self.movers[item])
        #self.canvas.lift(self.circles[item])

        self.movers = [self.mover1,self.mover2,self.mover3,self.mover4,self.mover5]
        self.hiders = [self.hider1,self.hider2,self.hider3,self.hider4,self.hider5]
        
        #print(self.uhr.anzeigen(), 'class init GUI done')
        self.update_time()              # Starts the mainloop
        ###### Init finished #####


    def callback(self, *event):
        ''' callback functions when the variables for the time and the start button is changed.
        Its used to control the speed of the animation. '''
        self.speed = self.speed_slider.get()

        if 0 < self.speed and self.speed <= 10:
            self.speed = self.speed / 2
        elif 10 < self.speed and self.speed <= 20:
            self.speed = self.speed / 5
        elif 20 < self.speed and self.speed <= 30:
            self.speed = self.speed / 2.5
        elif 30 < self.speed and self.speed <= 40:
            self.speed = self.speed / 2
        elif 40 < self.speed and self.speed <= 50:
            self.speed = self.speed / 1.5
            
        self.counter += self.speed

        if self.start_stop.get():                   # animates the genereal movement without any time correspondance
            if  self.counter >= 100:
                self.counter = 0
                self.animation_mover()
                #print(self.uhr.anzeigen())
                
        if self.time_anim_status.get():             # animates the pulsar only when the "actual time" simulation is chosen
            self.animation_pulsar()

    def update_time(self):
        ''' Updates the actual time and represents the mainloop. This function is called as dast as possible. '''
        self.strv_uhrzeit.set(self.uhr.anzeigen())                          # Loop for animation
        self.strv_zeitangabe.set('actual time:'+self.uhr.hms_anzeigen())                   # Show the actual time on top of the window

        if self.hms_last_cycle <> self.strv_zeitangabe.get():               # check if 1 second has passed
            self.time_animation()                                           # Call this function only when values changes (every 1s)
        self.hms_last_cycle = self.strv_zeitangabe.get()                    # copy the actual value to the last cycle variable
                                                                                                  
        self.master.after(self.cycle_time, self.update_time)                # Mainloop

    def exit_program(self,*event):
        ''' Close the window and exit the program '''
        self.master.destroy()

    def startbutton(self):
        ''' Starts or stops the animation '''
        if not self.start_stop.get():
            if self.time_anim_status.get():                     # when the time simulation is running, stop it first
                self.time_animation_start_stop()
            self.start_stop.set(True)
            self.bStart.config(text='Simulation Stop',bg='green')
            self.pulsar.reset()
        else:
            self.start_stop.set(False)
            self.bStart.config(text='Simulation Start',bg='red')            

    def time_animation_start_stop(self):
        ''' Starts or stops the animation according to the actual time '''       
        if not self.time_anim_status.get():
            if self.start_stop.get():                            # when the simulation is running, stop it first
                self.startbutton()
            self.time_anim_status.set(True)
            self.bAnimTime.config(text='Time Animation Stop',bg='green')
            self.time_animation()
        else:
            self.time_anim_status.set(False)
            self.bAnimTime.config(text='Time Animation Start',bg='blue')

    def time_animation(self,*event):
        ''' time animation function. This function is called every 1s. '''        
        self.time_in_s = self.uhr.time_in_s()
        
        if self.time_anim_status.get():                                     # animates the actual time when the button Time Animation Start was pressed
            for item in range(len(self.movers)):                
                self.hiders[item].show_time(self.time_in_s,int(item+1))
                self.movers[item].show_time(self.time_in_s,int(item+1))
        
    def animation_mover(self):
        '''
        Mover Rotation Increments see http://www.bartelmus.org/binar-uhr/#more-136
        '''       
        for item in range(len(self.movers)):
            self.hiders[item].rotate(int(item+1))
            self.movers[item].rotate(int(item+1))

        self.sim_uhrzeit.set(self.uhr.time_from_s(self.simulation_counter)) # shows a simulated time
        self.simulation_counter =  self.simulation_counter+1                # updates the counter for the virtual seconds

    def animation_pulsar(self):
        ''' The animation of the pulsar in the middle of the window. This item represents the microseconds of ath actual second. '''
        self.micros = self.uhr.get_micros()
        self.pulsar.puls(self.micros)        

    def reset(self):
        '''Resets all moving items to its start position and stops all actions '''
        if self.start_stop.get():
            self.startbutton()
            
        if self.time_anim_status.get():
            self.time_animation_start_stop()     

        for element in self.hiders:
            element.reset()
        for element in self.movers:
            element.reset()        
        self.pulsar.reset()
        self.simulation_counter = 0
        self.sim_uhrzeit.set(self.uhr.time_from_s(self.simulation_counter))
        
class Marking_Circle_Outlines():
    '''  This are Marking Circle_Outlines on the canvas.
    This is only to have a better overview about the position of the Movers. '''    
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,radius_1,angle_1,radius_2,angle_2,fill,width):
        '''  This function creates an canvas line.
        The first step is to convert the given polar coords to karthesian coords and create the start and end point of the line.
        The 2nd step is to create the item'''
        self.canvas = canvas
        self.x1, self.y1 = self.conv_pol_to_kart_coords(radius_1,angle_1)
        self.x2, self.y2 = self.conv_pol_to_kart_coords(radius_2,angle_2)     
        item = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2, fill=fill, width=width, tags="marking_line")
        return item

    def conv_pol_to_kart_coords(self,radius,angle,offsetx=400,offsety=400):   # Funktion konvertiert Polar-Koords in Karthesische-Koords
        '''  This function converts polar coords to karthesian coords '''
        self.direction = 1
        self.angle_offset = 90
        self.angle = self.angle_offset - angle
        coord_x = offsetx+radius*round(cos(2*pi/360*self.direction*self.angle),3)    #berechnete Soll-Position am Kreis in X mit 3 Dezimalstellen
        coord_y = offsety+radius*round(sin(2*pi/360*self.direction*(-self.angle)),3) #berechnete Soll-Position am Kreis in Y mit 3 Dezimalstellen
        return coord_x, coord_y


class Circle_Outlines():
    '''  This are Circle_Outlines where the Movers move on top '''
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,width):
        '''  creates an canvas oval '''
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,outline=outline,width=width, tags='outline')
        return item

class Movers():
    '''  This are moving objects to show the actual time '''
    def __init__(self, canvas, *args, **kwargs):
        self.ValueDict = {1:1, 2:16, 3:128, 4:512, 5:1024}
        
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lower(self.id)
        self.angleOffset = -90
        self.direction= -1
        self.angle = 0


    def create(self,canvas,x,y,r,start,extent,outline,fill,width):
        '''  creates an canvas arc '''
        self.canvas = canvas
        item = canvas.create_arc(x-r,y-r,x+r,y+r,start=start,extent=extent,outline=outline,fill=fill,width=width, tags='mover')
        return item
     
    def rotate(self,ident):
        '''
        rotates the object with the given increment    The Incredements each mover should move per one cycle
        mover1_inc = 360/86400*1           
        mover2_inc = 360/86400*16
        mover3_inc = 360/86400*128
        mover4_inc = 360/86400*512
        mover5_inc = 360/86400*1024
        '''
        #x1, y1, x2, y2 = self.canvas.bbox(self.id)
        self.ident = ident
        self.value = self.ValueDict[self.ident]
        self.increment = 360 / 86400 * self.value
          
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))       # Changes the start angle

        if self.angle >= 360.0:
            self.angle -= 360.0
        self.angle += self.increment

    def show_time(self,time_in_s,ident):
        '''
        shows its value according to the given time.        Mover position corresponding to the actual time
        self.mover1_value = 360/86400*1*self.time_in_s         
        self.mover2_value = 360/86400*16*self.time_in_s
        self.mover3_value = 360/86400*128*self.time_in_s
        self.mover4_value = 360/86400*512*self.time_in_s
        self.mover5_value = 360/86400*1024*self.time_in_s
        formula: calc_position = self.direction * (self.angleOffset + (360 / 86400 * self.moverX_value * self.time_in_s))
        '''
        self.time_in_s = time_in_s
        self.ident = ident
        self.value = self.ValueDict[self.ident]
        calc_position = self.direction * (self.angleOffset + (360 / 86400 * self.value * self.time_in_s)) # calculates the position according to the actual time
        self.canvas.itemconfig(self.id, start=calc_position)        # Changes the start angle according to the given value
        #print (self.time_in_s, self.ident, self.value, calc_position)
               
    def reset(self):
        '''  reset to start position '''
        self.angle = 0
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))

        
class Pulsar():
    '''  This is the Pulsar shown in the middle of the window. It represents the micro seconds according to its size.
    1000 micros represent the biggest size and 0 the minimum with an radius of 0. '''
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,fill,width):
        '''  creates an canvas oval '''
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,outline=outline, fill=fill ,width=width,tags='pulsar')
        return item
        
    def puls(self,micros):
        '''  adjusts the sizes of the object according to the given value '''
        self.micros = micros
        micros_len_pointer = self.micros/1000 * 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer, 400 + micros_len_pointer, 400 + micros_len_pointer)

    def reset(self):
        '''  reset to start position '''
        micros_len_pointer = 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer, 400 + micros_len_pointer, 400 + micros_len_pointer)     


def main():
    '''  main program '''
    root = tk.Tk()
    ui = GUI(root)
    root.mainloop()


if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@sepplx123: irgendwie erkenne ich nicht, was Du jetzt an der Klasse Uhrzeit verbessert hast. Sie wird mit unsinnigen Werten initialisiert. anzeigen und hms_anzeigen aktualisieren die Zeit und zeigen sie nicht nur an (oder geben sie als String zurück), das ist überraschend, also ein schwerer Designfehler. Statt die Zeit in die vielen Attribute aufzuteilen, kann man auch direkt das Formatieren von datetime nehmen. values ist gar ganz überflüssig, weil ein datetime-Objekt schon alle Informationen enthält, die Du in ein anonymes Tuple konvertierst. Alles in allem, lösch die Klasse und nimm datetime direkt.
BlackJack

@Uhrzeit: Also beim Lesen hänge ich immer noch beim `Uhrzeit`-Objekt. Da geben die ”Methoden” immer noch sowohl die Ergebnisse zurück und setzen sie als Attribute, das aber anscheinend auch inkonsistent. Je nach dem welche Methode man aufruft repräsentieren die verschiedenen Attribute nicht immer die gleiche Zeit. Für mich sieht das immer noch danach aus als wenn da Funktionen in eine Klasse gesteckt wurden die da nicht rein gehören.

Was die Positionsberechnung der Zeiger angeht: Die Trennung von GUI und Programmlogik macht man ja um zum Beispiel die GUI austauschen zu können ohne die Programmlogik neu schreiben zu müssen, oder die Programmlogik testen oder wiederverwenden zu können ohne eine GUI haben zu müssen. Deswegen sollte die logische Berechnung der Zeigerpositionen nicht in der GUI stecken. Da gehört nur die Umrechnung in GUI-Koordinaten rein.
sepplx123
User
Beiträge: 13
Registriert: Montag 14. Dezember 2015, 04:13

Sirius3 hat geschrieben:@sepplx123: irgendwie erkenne ich nicht, was Du jetzt an der Klasse Uhrzeit verbessert hast. Sie wird mit unsinnigen Werten initialisiert. anzeigen und hms_anzeigen aktualisieren die Zeit und zeigen sie nicht nur an (oder geben sie als String zurück), das ist überraschend, also ein schwerer Designfehler. Statt die Zeit in die vielen Attribute aufzuteilen, kann man auch direkt das Formatieren von datetime nehmen. values ist gar ganz überflüssig, weil ein datetime-Objekt schon alle Informationen enthält, die Du in ein anonymes Tuple konvertierst. Alles in allem, lösch die Klasse und nimm datetime direkt.
Okay, ich muss sagen du hast vollkommen Recht. Ich habe nochmals darüber geschlafen und dann, so denke ich, verstanden was ihr beide mir damit sagen wollt.
Alles was ich mit meiner Klasse Uhrzeit machen wollte existiert bereits schon im Objekt von datetime. Es ist daher unsinnig die Werte nochmals umzubenennen und von dieser Klasse zurückgeben zu lassen.
Auch sollte man nicht mehrmals pro Zyklus sich eine neue Uhrzeit geben lassen, da man dann Murks erhält. Manche Funktionen nehmen die vorherigen Werte und die nachfolgenden arbeiten mit aktualisierten weiter. Mischen sollte man tunlichst vermeiden.
Habe ich das soweit richtig verstanden und nun besser umgesetzt?
BlackJack hat geschrieben:@Uhrzeit: Also beim Lesen hänge ich immer noch beim `Uhrzeit`-Objekt. Da geben die ”Methoden” immer noch sowohl die Ergebnisse zurück und setzen sie als Attribute, das aber anscheinend auch inkonsistent. Je nach dem welche Methode man aufruft repräsentieren die verschiedenen Attribute nicht immer die gleiche Zeit. Für mich sieht das immer noch danach aus als wenn da Funktionen in eine Klasse gesteckt wurden die da nicht rein gehören.

Was die Positionsberechnung der Zeiger angeht: Die Trennung von GUI und Programmlogik macht man ja um zum Beispiel die GUI austauschen zu können ohne die Programmlogik neu schreiben zu müssen, oder die Programmlogik testen oder wiederverwenden zu können ohne eine GUI haben zu müssen. Deswegen sollte die logische Berechnung der Zeigerpositionen nicht in der GUI stecken. Da gehört nur die Umrechnung in GUI-Koordinaten rein.
Auch dir nochmals Danke für dein Feedback. Ich habe den "Käse" mal entfernt und versucht besser zu verarbeiten.
Die Uhrzeitabfrage findet nur 1mal am Anfang eines Zyklus statt und alle Funktionen verarbeiten nur diese Werte. Unnötige Variablen, Funktionen und Klassen habe ich entfernt. Ebenfalls habe ich viele Funktionen umbenannt, damit es übersichtlicher wird.
Ich habe gestern ehrlich gesagt selber den überblickt verloren, wie genau der Ablauf des Programms ist und richtiger-weise sein soll.
Nun gefällt es mir gleich sehr viel besser. :wink:

Das Programm bietet, wie einen Beitrag vorher bereit erwähnt, 2 Möglichkeiten. Zum einen eine Simulation mit einem Geschwindigkeitsregler und zum anderen die Anzeige der aktuellen Uhrzeit. In beiden Fällen ruft die Gui die jeweiligen Funktionen samt Parameterübergabe auf.
Was mir aber noch nicht gefällt ist, dass die Zeichnung der neuen Position ebenfalls nicht in der Gui sondern in der jeweiligen Klasse gemacht wird.

Sollte man das noch ändern oder wie wäre schlüssig?
Habt Ihr noch andere Vorschläge was man verbessern sollte?

Code: Alles auswählen

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

from __future__ import (absolute_import, division,print_function, unicode_literals)
from math import (sin, cos, pi)
import Tkinter as tk
from datetime import datetime

    
class GUI(object):
    """  Creates and draws all ui elements  in the window. """
    def __init__(self, master):
        self.master = master
        self.width = 800
        self.height = 800       
        self.cycle_time = 1                         # time in ms for one program cycle
        self.act_time = datetime.now().time()
        self.hours = 0
        self.minutes = 0
        self.seconds = 0
        self.micros = 0

        self.color_bg =                 '#ffffff'
        self.color_movers =             '#00bfff'   #'#4169e1' #
        self.color_m_Circle_Outlines =  '#87cefa'   #'#6495ed' #
        marking_items = []
        self.counter = 0
        self.micros = 0
        self.time_last_cycle = 0
        self.movers = []
        self.hiders = []
        self.simulation_counter = 0
        
        self.start_stop = tk.BooleanVar()           # Start/Stop the simulation
        self.start_stop.set(False)
        self.strv_zeitangabe = tk.StringVar()       # actual time shown above
        self.strv_zeitangabe.set('00:00:00')
        self.sim_uhrzeit = tk.StringVar()           # displayed time for simulation 
        self.sim_uhrzeit.set('00:00:00')       
        self.time_anim_status = tk.BooleanVar()     # Start/Stop real time animation
        self.time_anim_status.set(False)

        # Frames             
        self.fr1 = tk.Frame(self.master, width=100, height=50, background='grey')
        self.fr2 = tk.Frame(self.master, width=100, height=50, background='grey')
        self.fr3 = tk.Frame(self.master, width=100, height=20, background='grey')
        self.fr1.pack(expand=1,fill='both')
        self.fr2.pack(expand=1,fill='both', anchor='center')
        self.fr3.pack(expand=1,fill='both')

        # Buttons
        self.bStart = tk.Button(self.fr2,width=0, height=1, bg='red',bd=5, 
                                text = "Simulation Start", command = self.startbutton )
        self.bStart.grid(row=2,column=0,padx=10, pady=10, sticky='nw')
        self.bReset = tk.Button(self.fr2,width=0, height=1, bg='red',bd=5, 
                                text = "Simulation Reset", command = self.reset)
        self.bReset.grid(row=2,column=1,padx=10, pady=10, sticky='nw')
        self.bQuit = tk.Button(self.fr1,padx=0, pady=5,width=10, height=1,bd=5, bg='red', 
                               text = "Quit", command = self.exit_program)
        self.bQuit.pack(side='right', padx=10, anchor='center')     
        self.bAnimTime = tk.Button(self.fr2,width=30,padx=0, pady=5, height=1,bd=5, bg='blue', 
                                   text = "Real Time Animation Start", command = self.realtime_animation_start_stop)      
        self.bAnimTime.grid(row=0,column=0,columnspan=2,padx=10, pady=5, sticky='nw')

        # Scales
        self.speed_label = tk.Label(self.fr2, bg='red', highlightthickness=1, height=2, text='Speed Adjustment')
        self.speed_label.grid(row=3,column=0,ipadx=0,pady=0,sticky='ne')
        self.speed_slider = tk.Scale(self.fr2, bg='red',length=300, troughcolor='black', 
                                     highlightbackground='blue', highlightthickness=0, 
                                     from_=1, to=100, orient='vertical')
        self.speed_slider.set(10)
        self.speed_slider.grid(row=3,column=1,ipadx=0,pady=0,sticky='n')
        
        # Canvas         
        self.canvas = tk.Canvas(self.fr2, width = self.width, height = self.height, 
                                highlightthickness=0, background=self.color_bg)
        self.canvas.grid(row=0,column=2,rowspan=10)

        # Labels
        self.label_uhrzeit = tk.Label(self.fr1, textvariable=self.strv_zeitangabe,
                                      bg='grey',fg='red',width=50, height=1,font=("Arial", 20))
        self.label_uhrzeit.pack(side='left',padx=0,ipadx=0, anchor='center')
        
        self.label_Simulation = tk.Label(self.fr2, text='Simulated time',bg='white',fg='red',
                                         width=20, height=2,font=("Arial", 15))
        self.label_Simulation.grid(row=1,column=0,columnspan=2, ipadx=0,ipady=10,sticky='n')       
        self.label_sim_uhrzeit = tk.Label(self.fr2, textvariable=self.sim_uhrzeit,bg='white',fg='red',
                                          width=20, height=0,font=("Arial", 15))
        self.label_sim_uhrzeit.grid(row=1,column=0,columnspan=2,ipadx=0,ipady=10,pady=0,sticky='s')
        

        # Text Items
        

        # Layout Design:   Circle_Outlines ==> Movers ==> Hiders ==> Pulsar
        marking_angle = [360-360,360/2]
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70,item,
                                                         fill=self.color_m_Circle_Outlines,width=2)) # 0, 180°
        marking_angle = [360/4,360-360/4]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60,item,
                                                         fill=self.color_m_Circle_Outlines,width=2)) # 90, 270°
        marking_angle = [360/8*1,360/8*3,360/8*5,360/8*7]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*2,item,
                                                         fill=self.color_m_Circle_Outlines,width=2)) #
        marking_angle = [360/16*1,360/16*3,360/16*5,360/16*7,360/16*9,360/16*11,360/16*13,360/16*15]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*3,item,
                                                         fill=self.color_m_Circle_Outlines,width=2)) #
        marking_angle = [360/32*1, 360/32*3, 360/32*5, 360/32*7,360/32*9,360/32*11,360/32*13,
                         360/32*15,360/32*17,360/32*19,360/32*21, 360/32*23,360/32*25,360/32*27,360/32*29,360/32*31]       
        for item in marking_angle:
            marking_items.append(Marking_Circle_Outlines(self.canvas,370,item,70+60*4,item,
                                                         fill=self.color_m_Circle_Outlines,width=2)) #
        
        self.line1 = Circle_Outlines(self.canvas,400,400,340,outline=self.color_m_Circle_Outlines,width=2) 
        self.mover1 = Movers(self.canvas, x=400, y=400, r=360, start=90, extent=(360/32), 
                             outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider1 = Movers(self.canvas, x=400, y=400, r=320, start=90, extent=(360/32)+2, 
                             outline=self.color_bg, fill=self.color_bg ,width=3)

        self.line2 = Circle_Outlines(self.canvas,400,400,280,outline=self.color_m_Circle_Outlines,width=2)
        self.mover2 = Movers(self.canvas, x=400, y=400, r=300, start=90, extent=(360/16),
                             outline=self.color_movers, fill=self.color_movers,width=1)
        self.hider2 = Movers(self.canvas, x=400, y=400, r=260, start=90, extent=(360/16)+2, 
                             outline=self.color_bg, fill=self.color_bg ,width=3)
                      
        self.line3 = Circle_Outlines(self.canvas,400,400,220,outline=self.color_m_Circle_Outlines,width=2)
        self.mover3 = Movers(self.canvas, x=400, y=400, r=240, start=90, extent=(360/8), 
                             outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider3 = Movers(self.canvas, x=400, y=400, r=200, start=90, extent=(360/8)+2, 
                             outline=self.color_bg, fill=self.color_bg ,width=3)
        
        self.line4 = Circle_Outlines(self.canvas,400,400,160,outline=self.color_m_Circle_Outlines,width=2)
        self.mover4 = Movers(self.canvas, x=400, y=400, r=180, start=90, extent=(360/4), 
                             outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider4 = Movers(self.canvas, x=400, y=400, r=140, start=90, extent=(360/4)+2, 
                             outline=self.color_bg, fill=self.color_bg, width=3)
                             
        self.line5 = Circle_Outlines(self.canvas,400,400,100,outline=self.color_m_Circle_Outlines,width=2)
        self.mover5 = Movers(self.canvas, x=400, y=400, r=120, start=90, extent=(360/2), 
                             outline=self.color_movers, fill=self.color_movers ,width=1)
        self.hider5 = Movers(self.canvas, x=400, y=400, r=80, start=90, extent=(360/2)+2, 
                             outline=self.color_bg, fill=self.color_bg ,width=3)
        # Pulsar
        self.pulsar = Pulsar(self.canvas,400,400,60,outline=self.color_movers,fill=self.color_movers,width=1)  
        #self.canvas.lift(self.movers[item])
        #self.canvas.lift(self.circles[item])

        self.movers = [self.mover1,self.mover2,self.mover3,self.mover4,self.mover5]
        self.hiders = [self.hider1,self.hider2,self.hider3,self.hider4,self.hider5]
        
        #print(self.uhr.anzeigen(), 'class init GUI done')
        self.mainloop()          # Starts the mainloop
        ###### Init finished #####

    def mainloop(self):
        """ Updates the actual time and represents the mainloop.
        This function is called as fast as possible. """
        
        #Step1: get the actual time
        self.act_time = self.get_act_time()
        
        #Step2: Forward the variables to update the actual time shown on top of the window          
        self.strv_zeitangabe.set(                                                    
                                 'actual time: {0:02d}:{1:02d}:{2:02d}'.format 
                                 (self.act_time.hour,self.act_time.minute,self.act_time.second)
                                 )
        
        #Step3: Call the simulation function
        self.time_simulation()
        
        #Step4: Check if 1s has passed to call the function for the real time animation
        if self.time_last_cycle <> self.act_time.second:    # check if 1 second has passed
            self.realtime_animation()                       # Call this function only when values changes (every 1s)
        self.time_last_cycle = self.act_time.second         # copy the actual value to the last cycle variable


        #Step5: start the loop again                                                                                         
        self.master.after(self.cycle_time, self.mainloop)    # mainloop

    def startbutton(self):
        """ Starts or stops the animation """
        if not self.start_stop.get():
            if self.time_anim_status.get():                     # when the time simulation is running, stop it first
                self.realtime_animation_start_stop()
            self.start_stop.set(True)
            self.bStart.config(text='Simulation Stop',bg='green')
            self.pulsar.reset()
        else:
            self.start_stop.set(False)
            self.bStart.config(text='Simulation Start',bg='red')            

    def realtime_animation_start_stop(self):
        """ Starts or stops the animation according to the actual time """       
        if not self.time_anim_status.get():
            if self.start_stop.get():                            # when the simulation is running, stop it first
                self.startbutton()
            self.time_anim_status.set(True)
            self.bAnimTime.config(text='Real Time Animation Stop',bg='green')
            self.realtime_animation()
        else:
            self.time_anim_status.set(False)
            self.bAnimTime.config(text='Real Time Animation Start',bg='blue')

    def time_simulation(self, *event):
        """ time_simulation functions when the variables for the time or the start button are changed.
        Its used to control the speed of the animation. """
        self.speed = self.speed_slider.get()

        if 0 < self.speed and self.speed <= 10:
            self.speed = self.speed / 2
        elif 10 < self.speed and self.speed <= 20:
            self.speed = self.speed / 5
        elif 20 < self.speed and self.speed <= 30:
            self.speed = self.speed / 2.5
        elif 30 < self.speed and self.speed <= 40:
            self.speed = self.speed / 2
        elif 40 < self.speed and self.speed <= 50:
            self.speed = self.speed / 1.5
            
        self.counter += self.speed

        if self.start_stop.get():       # animates the general movement without any time correspondence
            if  self.counter >= 100:
                self.counter = 0
                self.animation_mover()
                
        if self.time_anim_status.get(): # animates the pulsar only when the "real time" simulation is chosen
            self.animation_pulsar()


    def realtime_animation(self,*event):
        """ time animation function. This function is called every 1s.
        animates the actual time when the button Time Animation Start was pressed"""
        if self.time_anim_status.get():                      
            for item in range(len(self.movers)):                
                self.hiders[item].show_time(self.time_in_s(self.act_time),int(item+1))
                self.movers[item].show_time(self.time_in_s(self.act_time),int(item+1))
        
    def animation_mover(self):
        """ Mover Rotation Increments without time reference
        http://www.bartelmus.org/binar-uhr/#more-136 """       
        for item in range(len(self.movers)):
            self.hiders[item].rotate(int(item+1))
            self.movers[item].rotate(int(item+1))

        self.sim_uhrzeit.set(self.time_from_s(self.simulation_counter)) # shows a simulated time
        self.simulation_counter += 1  # updates the counter for the virtual seconds

    def animation_pulsar(self):
        """ The animation of the pulsar in the middle of the window.
        This item represents the microseconds of the actual second. """
        self.pulsar.puls(self.act_time.microsecond)        

    def reset(self):
        """Resets all moving items to its start position and stops all actions """
        #Step1: stop all active simulations first
        if self.start_stop.get():
            self.startbutton()  
        if self.time_anim_status.get():
            self.realtime_animation_start_stop()
            
        #Step2: reset the movers
        for element in self.hiders:
            element.reset()
        for element in self.movers:
            element.reset()
            
        #Step3: reset the pulsar and the simulation time
        self.pulsar.reset()
        self.simulation_counter = 0
        self.sim_uhrzeit.set(self.time_from_s(self.simulation_counter))

    def get_act_time(self):
        """  Returns the values of the actual time """
        return datetime.now().time()       

    def time_in_s(self,given_time):
        """  Transforms the actual time in seconds """
        self.time_s = (given_time.hour*3600) + (given_time.minute*60) + given_time.second
        return self.time_s
    
    def time_from_s(self,time_in_s):
        """  Transforms the given time in seconds back to the format hh:mm:ss """
        self.time_s = time_in_s
        hours, rest = divmod(self.time_s, 3600)
        minutes, seconds = divmod(rest, 60)
        return ("{0:02d}:{1:02d}:{2:02d}".format(hours,minutes,seconds))

    def exit_program(self,*event):
        """ Close the window and exit the program """
        self.master.destroy()
        
class Marking_Circle_Outlines():
    """  This are Marking Circle_Outlines on the canvas.
    This is only to have a better overview about the position of the Movers. """    
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,radius_1,angle_1,radius_2,angle_2,fill,width):
        """  This function creates an canvas line.
        The first step is to convert the given polar coords to karthesian coords
        and create the start and end point of the line.
        The 2nd step is to create the item"""
        self.canvas = canvas
        self.x1, self.y1 = self.conv_pol_to_kart_coords(radius_1,angle_1)
        self.x2, self.y2 = self.conv_pol_to_kart_coords(radius_2,angle_2)     
        item = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2,
                                       fill=fill, width=width, tags="marking_line")
        return item

    def conv_pol_to_kart_coords(self,radius,angle,offsetx=400,offsety=400):
        """  This function converts polar coords to karthesian coords """
        self.direction = 1
        self.angle_offset = 90
        self.angle = self.angle_offset - angle
        #berechnete Soll-Position am Kreis in X mit 3 Dezimalstellen
        coord_x = offsetx+radius*round(cos(2*pi/360*self.direction*self.angle),3)
        #berechnete Soll-Position am Kreis in Y mit 3 Dezimalstellen
        coord_y = offsety+radius*round(sin(2*pi/360*self.direction*(-self.angle)),3)
        return coord_x, coord_y

class Circle_Outlines():
    """  This are Circle_Outlines where the Movers move on top """
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,width):
        """  creates an canvas oval """
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,
                                  outline=outline,width=width, tags='outline')
        return item

class Movers():
    """  This are moving objects to show the actual time """
    def __init__(self, canvas, *args, **kwargs):
        self.ValueDict = {1:1, 2:16, 3:128, 4:512, 5:1024}
        
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lower(self.id)
        self.angleOffset = -90
        self.direction= -1
        self.angle = 0

    def create(self,canvas,x,y,r,start,extent,outline,fill,width):
        """  creates an canvas arc """
        self.canvas = canvas
        item = canvas.create_arc(x-r,y-r,x+r,y+r,start=start,extent=extent,
                                 outline=outline,fill=fill,width=width, tags='mover')
        return item
     
    def rotate(self,ident):
        """
        rotates the object with the given increment
        The Incredements each mover should move per one cycle
        mover1_inc = 360/86400*1           
        mover2_inc = 360/86400*16
        mover3_inc = 360/86400*128
        mover4_inc = 360/86400*512
        mover5_inc = 360/86400*1024
        """
        #x1, y1, x2, y2 = self.canvas.bbox(self.id)
        self.ident = ident
        self.value = self.ValueDict[self.ident]
        self.increment = 360 / 86400 * self.value
          
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset)) # Changes the start angle

        if self.angle >= 360.0:
            self.angle -= 360.0
        self.angle += self.increment

    def show_time(self,time_in_s,ident):
        """
        Mover position corresponding to the actual time
        self.mover1_value = 360/86400*1*self.time_in_s         
        self.mover2_value = 360/86400*16*self.time_in_s
        self.mover3_value = 360/86400*128*self.time_in_s
        self.mover4_value = 360/86400*512*self.time_in_s
        self.mover5_value = 360/86400*1024*self.time_in_s
        calc_position = self.direction * (self.angleOffset + (360 / 86400 * self.moverX_value * self.time_in_s))
        """
        self.time_in_s = time_in_s
        self.ident = ident
        self.value = self.ValueDict[self.ident]
        calc_position = self.direction * (self.angleOffset + (360 / 86400 * self.value * self.time_in_s))
        self.canvas.itemconfig(self.id, start=calc_position) # Changes the start angle according to the given value
        #print (self.time_in_s, self.ident, self.value, calc_position)
               
    def reset(self):
        """  reset to start position """
        self.angle = 0
        self.canvas.itemconfig(self.id, start=self.direction*(self.angle+self.angleOffset))

        
class Pulsar():
    """
    This is the Pulsar shown in the middle of the window.
    It represents the micro seconds according to its size.
    1000 micros represent the biggest size and 0 the minimum with an radius of 0.
    """
    def __init__(self, canvas, *args, **kwargs):
        self.canvas = canvas
        self.id = self.create(self.canvas, *args, **kwargs)        
        #self.canvas.lift(self.id)

    def create(self,canvas,x,y,r,outline,fill,width):
        """  creates an canvas oval """
        self.canvas = canvas
        item = canvas.create_oval(x-r,y-r,x+r,y+r,outline=outline, fill=fill ,width=width, tags='pulsar')
        return item
        
    def puls(self,micros):
        """  adjusts the sizes of the object according to the given value """
        self.micros = int(micros/1000)
        micros_len_pointer = self.micros/1000 * 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer,
                           400 + micros_len_pointer, 400 + micros_len_pointer)

    def reset(self):
        """  reset to start position """
        micros_len_pointer = 60
        self.canvas.coords(self.id, 400 - micros_len_pointer, 400 - micros_len_pointer,
                           400 + micros_len_pointer, 400 + micros_len_pointer)


def main():
    """  main program """
    root = tk.Tk()
    ui = GUI(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@sepplx123: schau nochmal über Dein gesamtes Programm und entscheide, was hier Attribute oder Methoden von Klassen sein müssen, und was eigentlich keine sind. Zudem sollten alle Attribute schon in __init__ mit Werten belegt werden. Das hilft auch zu entscheiden, was kein Attribut sein sollte. Zudem sollte eine Methode entweder ein Attribut setzen oder einen Wert zurückgeben, nicht beides. Ein Beispiel, Marking_Circle_Outlines: x1, y1, x2, y2, direction, angle_offset und angle werden nicht in __init__ gesetzt und sollten auch gar keine Attribute sein. conv_pol_to_kart_coords braucht gar kein self, ist damit also keine Methode. __init__ übergibt nur auf magische Weise alle Parameter an create, create könnte also gleich __init__ heißen:

Code: Alles auswählen

from math import radians

def conv_pol_to_kart_coords(radius, angle, offsetx=400, offsety=400, angle_offset=90):
    """  This function converts polar coords to karthesian coords """
    angle = radians(angle_offset - angle)
    coord_x = offsetx + radius * round(cos(angle),3)
    coord_y = offsety - radius * round(sin(angle),3)
    return coord_x, coord_y

class Marking_Circle_Outlines():
    """  This are Marking Circle_Outlines on the canvas.
   This is only to have a better overview about the position of the Movers. """    
    def __init__(self,canvas,radius_1,angle_1,radius_2,angle_2,fill,width):
        """  This function creates an canvas line.
       The first step is to convert the given polar coords to karthesian coords
       and create the start and end point of the line.
       The 2nd step is to create the item"""
        self.canvas = canvas
        x1, y1 = conv_pol_to_kart_coords(radius_1,angle_1)
        x2, y2 = conv_pol_to_kart_coords(radius_2,angle_2)    
        self.id = canvas.create_line(x1, y1, x2, y2,
                                     fill=fill, width=width, tags="marking_line")
damit besteht aber die Klasse Marking_Circle_Outlines nur noch aus __init__, kann also in eine Funktion umgewandelt werden. Diese Aufgabe und das finden ähnlicher unnötiger Attribute, Methoden und Klassen überlasse ich Dir als Übung.
sepplx123
User
Beiträge: 13
Registriert: Montag 14. Dezember 2015, 04:13

Vielen Dank für deine Rückmeldung und Anregungen Sirius3.

Ich habe mich mal daran gemacht diese so weit ich es verstanden habe umzusetzen. Es sieht nun deutlich besser aus als vorher und ich habe wieder etwas gelernt.

Ich habe nun einiges geändert und bräuchte mal wieder ein zweites oder drittes Paar Augen, die sich das Ganze mal anschauen und mir weitere Tipps zur Optimierung und besseren Strukturierung geben.

Die Zeiger haben nun einen Farbverlauf von 2 Farben und bestehen aus einzelnen Linie die dann dieses Kreisbogensegment bilden. Optisch 1a aber es zieht leider extrem an der Performance. Ursache ist leider nun, dass jeder Zeiger aus 1000 Linien besteht, die jeweils neu positioniert werden.
Kann mir hier jemand einen Tipp geben, wie die Animation wieder flüssig läuft?
Und gibt es eine andere Möglichkeit so ein Kreisbogensegment anders zu erstellen? Einfarbig ist auch okay. Ihc wollte den Farbverlauf halt mal ausprobieren.

Ansonsten sind weitere Anregungen gerne willkommen. :D

Meine alten Versionen gibts auch auf github: https://github.com/sepplx123/python-tk- ... y_clock.py

Code: Alles auswählen

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

from __future__ import (absolute_import, division, print_function, unicode_literals)
from math import (sin, cos, pi)
from datetime import datetime

# Fix for Python 2.x/3.x
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
try:
    import tkMessageBox as msgbox
except ImportError:
    import tkinter.messagebox as msgbox


def conv_pol_to_kart_coords(radius, angle, angle_offset, offsetx, offsety):
    """  This function converts polar coords to karthesian coords """
    angle = angle_offset + angle
    x = offsetx + radius * round(cos(2 * pi / 360 * angle), 3)  # X with 3 decimals
    y = offsety - radius * round(sin(2 * pi / 360 * angle), 3)  # Y with 3 decimals
    return x, y

def circle_outline(canvas, x, y, r, color, width):
    """  This are Circle_Outlines where the Movers move on top
    The function creates a canvas oval with a colored outline.
    canvas: ==> area where it should be created
    x, y, r ==> parameters for the circle
    color, width ==> parameters for the visual information """
    item = canvas.create_oval(x - r, y - r, x + r, y + r, outline=color, width=width, tags='circle_outline')
    canvas.lower(item)
    return item

def marking_circle_outline(canvas, radius_1, angle_1, radius_2, angle_2,
                           angle_offset, offsetx, offsety, color, width):
    """ marking circle outlines are for better recognizing the time
    and divide the circle outlines in smaller pieces """
    x1, y1 = conv_pol_to_kart_coords(radius=radius_1, angle=angle_1,
                                     angle_offset=angle_offset, offsetx=offsetx, offsety=offsety)
    x2, y2 = conv_pol_to_kart_coords(radius=radius_2, angle=angle_2,
                                     angle_offset=angle_offset, offsetx=offsetx, offsety=offsety)
    item = canvas.create_line(x1, y1, x2, y2, fill=color, width=width, tags="marking_circle_outline")
    canvas.lower(item)
    return item

def create_canvas_elements(n, distance, canvas, width, height, color_markings, color_movers1, color_movers2,
                           dist_between_elements, dist_between_movers, angle_offset):
    """ The following items are drawn on the canvas:
        Marking_Circle_Outlines &  Circle_Outlines ==> Movers ==> Pulsar  """
    marking_items = []
    movers = []
    tempdict = {}  # empty dict for creating Marking_Circle_Outlines values
    for i in range(1, n + 1):  # Outer Loop for Circle_Outlines and Movers number = 1,2,3,4,5
        marking_items.append(circle_outline(canvas, width / 2, height / 2, 40 + distance * i,
                                            color=color_markings, width=2))
        item = Mover(canvas, radius1=dist_between_elements + (distance * i + dist_between_elements),
                     radius2=distance * (1 + i), start=0, extent=(360 / 2 ** i), angle_offset=angle_offset,
                     offsetx=width / 2, offsety=height / 2, color1=color_movers1, color2=color_movers2, width=2)
        movers.append(item)

        for j in range(0, 100 * 360, int(100 * 360 / 2 ** i)):  # inner loop for Marking_Circle_Outlines
            # j/100 to get the real angle back. int(100*360/2**i) is only used to get an integer value
            new_angle = j / 100
            if new_angle not in tempdict:  # Check if not already exist
                # print('i =',i,'new_angle =',new_angle)
                tempdict[new_angle] = i  # create a new entry
                item = marking_circle_outline(canvas=canvas, radius_1=dist_between_elements + distance * i,
                                              angle_1=new_angle, radius_2=dist_between_elements + (distance * (1 + n)),
                                              angle_2=new_angle, angle_offset=angle_offset, offsetx=width / 2,
                                              offsety=height / 2, color=color_markings,
                                              width=2)  # Intervall = 100*360/2**i
                marking_items.append(item)

                # Pulsar
    pulsar = (Pulsar(canvas, width / 2, height / 2, dist_between_movers, offsetx=width / 2,
                     offsety=height / 2, color1=color_movers1, color2=color_movers2, width=2))

    return marking_items, movers, pulsar

def color_mix(canvas, limit, color1, color2, i):
    """ Mixes 2 colors in the defined limit"""
    (r1, g1, b1) = canvas.winfo_rgb(color1)
    (r2, g2, b2) = canvas.winfo_rgb(color2)
    r_ratio = float(r2 - r1) / limit
    g_ratio = float(g2 - g1) / limit
    b_ratio = float(b2 - b1) / limit
    nr = int(r1 + (r_ratio * i))
    ng = int(g1 + (g_ratio * i))
    nb = int(b1 + (b_ratio * i))
    color = "#%4.4x%4.4x%4.4x" % (nr, ng, nb)
    return color


class GUI(object):
    """  Draws all ui elements  in the window. It's also the mainloop of the program """

    def __init__(self, master):
        self.master = master
        self.width = 800  # canvas width
        self.height = 800  # canvas height

        self.cycle_time = 1  # time in ms for one program cycle
        self.speed_counter = 0
        self.seconds_last_cycle = 0
        self.simulation_counter = 0

        # clock variables
        self.act_time = datetime.now().time()
        self.hours = 0
        self.minutes = 0
        self.seconds = 0
        self.micros = 0

        self.sim_start_stop = tk.BooleanVar()  # Start/Stop the simulation
        self.sim_start_stop.set(False)
        self.time_anim_status = tk.BooleanVar()  # Start/Stop realtime animation
        self.time_anim_status.set(False)
        self.strv_zeitangabe = tk.StringVar()  # actual time shown above
        self.strv_zeitangabe.set('00:00:00')
        self.sim_uhrzeit = tk.StringVar()  # displayed time for simulation
        self.sim_uhrzeit.set('00:00:00')

        # UI Variables
        self.no_movers = 5
        self.dist_between_movers = 60
        self.dist_between_elements = 10
        self.angle_offset = 90
        self.color_bg = '#ffffff'
        self.color_movers1 = '#00bfff'
        self.color_movers2 = '#0000ff'
        self.color_markings = '#87cefa'
        self.marking_items = []  # Items to divide the circle outlines in intervals
        self.movers = []  # Items to show the time that move on the canvas
        self.pulsar = None  # The item in the middle that shows the microseconds of the actual second

        # Frames
        self.fr1 = tk.Frame(self.master, width=100, height=20, background='grey')
        self.fr2 = tk.Frame(self.master, width=self.width, height=self.height, background='grey')
        self.fr3 = tk.Frame(self.master, width=100, height=20, background='grey')
        self.fr1.pack(expand=1, fill='both')
        self.fr2.pack(expand=1, fill='both', anchor='center')
        self.fr3.pack(expand=1, fill='both')

        # Buttons
        self.bStart = tk.Button(self.fr2, width=0, height=1, bg='red', bd=5,
                                text="Simulation Start", command=self.startbutton)
        self.bStart.grid(row=2, column=0, padx=10, pady=10, sticky='nw')
        self.bReset = tk.Button(self.fr2, width=0, height=1, bg='red', bd=5,
                                text="Simulation Reset", command=self.reset)
        self.bReset.grid(row=2, column=1, padx=10, pady=10, sticky='nw')
        self.bQuit = tk.Button(self.fr1, padx=0, pady=5, width=10, height=1, bd=5, bg='red',
                               text="Quit", command=self.myquit)  # self.exit_program
        self.bQuit.pack(side='right', padx=10, anchor='center')
        self.bAnimTime = tk.Button(self.fr2, width=30, padx=0, pady=5, height=1, bd=5, bg='blue',
                                   text="Real Time Animation Start", command=self.realtime_animation_start_stop)
        self.bAnimTime.grid(row=0, column=0, columnspan=2, padx=10, pady=5, sticky='nw')

        # Scales
        self.speed_label = tk.Label(self.fr2, bg='red', highlightthickness=1, height=2, text='Speed Adjustment')
        self.speed_label.grid(row=3, column=0, ipadx=0, pady=0, sticky='ne')
        self.speed_slider = tk.Scale(self.fr2, bg='red', length=300, troughcolor='black',
                                     highlightbackground='blue', highlightthickness=0,
                                     from_=1, to=100, orient='vertical')
        self.speed_slider.set(10)
        self.speed_slider.grid(row=3, column=1, ipadx=0, pady=0, sticky='n')

        # Canvas
        self.canvas = tk.Canvas(self.fr2, width=self.width, height=self.height,
                                highlightthickness=0, background=self.color_bg)
        self.canvas.grid(row=0, column=2, rowspan=10)

        # Labels
        self.label_uhrzeit = tk.Label(self.fr1, textvariable=self.strv_zeitangabe,
                                      bg='grey', fg='red', width=50, height=1, font=("Arial", 20))
        self.label_uhrzeit.pack(side='left', padx=0, ipadx=0, anchor='center')

        self.label_Simulation = tk.Label(self.fr2, text='Simulated time', bg='white', fg='red',
                                         width=20, height=2, font=("Arial", 15))
        self.label_Simulation.grid(row=1, column=0, columnspan=2, ipadx=0, ipady=10, sticky='n')
        self.label_sim_uhrzeit = tk.Label(self.fr2, textvariable=self.sim_uhrzeit, bg='white', fg='red',
                                          width=20, height=0, font=("Arial", 15))
        self.label_sim_uhrzeit.grid(row=1, column=0, columnspan=2, ipadx=0, ipady=10, pady=0, sticky='s')

        # Menubar "Datei"-Menue
        self.menubar = tk.Menu(self.master)
        self.filemenu = tk.Menu(self.menubar, tearoff=0)
        self.filemenu.add_command(label="##***#####****",
                                  command=None)
        self.filemenu.add_separator()
        self.filemenu.add_command(label="Programm beenden",
                                  command=self.exit_program)
        self.menubar.add_cascade(label="Datei", menu=self.filemenu)

        # "Hilfe"-Menue
        self.infomenu = tk.Menu(self.menubar, tearoff=0)
        self.infomenu.add_command(label="About", command=self.showInfo)
        self.menubar.add_cascade(label="Info", menu=self.infomenu)
        self.master.config(menu=self.menubar)

        # Text Items


        # Canvas Elements:   Marking_Circle_Outlines &  Circle_Outlines ==> Movers ==> Pulsar
        self.marking_items, self.movers, self.pulsar = create_canvas_elements(
                n=self.no_movers, distance=self.dist_between_movers, canvas=self.canvas, width=self.width,
                height=self.height,
                color_markings=self.color_markings, color_movers1=self.color_movers1, color_movers2=self.color_movers2,
                dist_between_elements=self.dist_between_elements, dist_between_movers=self.dist_between_movers,
                angle_offset=self.angle_offset
        )
        # print(self.uhr.anzeigen(), 'class init GUI done')
        self.main_loop()  # Starts the mainloop
        ###### Init finished #####

    def main_loop(self):
        """ Updates the actual time and represents the mainloop.
        This function is called as fast as possible. """

        # Step1: get the actual time
        self.act_time = self.get_act_time()

        # Step2: Forward the variables to update the actual time shown on top of the window
        self.strv_zeitangabe.set(
                'actual time: {0:02d}:{1:02d}:{2:02d}'.format
                (self.act_time.hour, self.act_time.minute, self.act_time.second)
        )

        # Step3: Call the simulation function
        self.time_simulation()

        # Step4: Check if 1s has passed to call the function for the real time animation
        if not self.seconds_last_cycle == self.act_time.second:  # check if 1 second has passed
            self.realtime_animation()  # Call this function only when values changes (every 1s)
        self.seconds_last_cycle = self.act_time.second  # copy the actual value to the last cycle variable

        # Step5: start the loop again
        self.master.after(self.cycle_time, self.main_loop)  # mainloop

    def startbutton(self):
        """ Starts or stops the animation """
        if not self.sim_start_stop.get():
            if self.time_anim_status.get():  # when the time simulation is running, stop it first
                self.realtime_animation_start_stop()
            self.sim_start_stop.set(True)
            self.bStart.config(text='Simulation Stop', bg='green')
            self.pulsar.reset()
        else:
            self.sim_start_stop.set(False)
            self.bStart.config(text='Simulation Start', bg='red')

    def realtime_animation_start_stop(self):
        """ Starts or stops the animation according to the actual time """
        if not self.time_anim_status.get():
            if self.sim_start_stop.get():  # when the simulation is running, stop it first
                self.startbutton()
            self.time_anim_status.set(True)
            self.bAnimTime.config(text='Real Time Animation Stop', bg='green')
            self.realtime_animation()
        else:
            self.time_anim_status.set(False)
            self.bAnimTime.config(text='Real Time Animation Start', bg='blue')

    def time_simulation(self, *event):
        """ time_simulation functions when the variables for the time or the start button are changed.
        Its used to control the speed of the animation. """
        self.speed = self.speed_slider.get()

        if 0 < self.speed and self.speed <= 10:
            self.speed = self.speed / 2
        elif 10 < self.speed and self.speed <= 20:
            self.speed = self.speed / 5
        elif 20 < self.speed and self.speed <= 30:
            self.speed = self.speed / 2.5
        elif 30 < self.speed and self.speed <= 40:
            self.speed = self.speed / 2
        elif 40 < self.speed and self.speed <= 50:
            self.speed = self.speed / 1.5

        self.speed_counter += self.speed

        if self.sim_start_stop.get():  # Simulation start / stop
            if self.speed_counter >= 100:  # Speed adjustment for the simulation
                self.speed_counter = 0
                self.animation_mover()

        if self.time_anim_status.get():  # animates the pulsar only when the real time simulation is chosen
            self.animation_pulsar()

    def realtime_animation(self, *event):
        """ time animation function. This function is called every 1s.
        animates the actual time when the button Time Animation Start was pressed"""
        if self.time_anim_status.get():
            for item in range(len(self.movers)):
                self.movers[item].show_time(self.time_in_s(self.act_time), ident=int(item + 1))

    def animation_mover(self):
        """ Mover Rotation Increments without time reference
        http://www.bartelmus.org/binar-uhr/#more-136 """
        for item in range(len(self.movers)):
            self.movers[item].rotate(ident=int(item + 1))

        self.sim_uhrzeit.set(self.time_from_s(self.simulation_counter))  # shows a simulated time
        self.simulation_counter += 1  # updates the counter for the virtual seconds

    def animation_pulsar(self):
        """ The animation of the pulsar in the middle of the window.
        This item represents the microseconds of the actual second. """
        self.pulsar.puls(self.act_time.microsecond)

    def reset(self):
        """Resets all moving items to its start position and stops all actions """
        # Step1: stop all active simulations first
        if self.sim_start_stop.get():
            self.startbutton()
        if self.time_anim_status.get():
            self.realtime_animation_start_stop()

        # Step2: reset the movers
        for item in self.movers:
            item.reset()

        # Step3: reset the pulsar and the simulation time
        self.pulsar.reset()
        self.simulation_counter = 0
        self.sim_uhrzeit.set(self.time_from_s(self.simulation_counter))

    def get_act_time(self):
        """  Returns the values of the actual time """
        return datetime.now().time()

    def time_in_s(self, given_time):
        """  Transforms the actual time in seconds """
        self.time_s = (given_time.hour * 3600) + (given_time.minute * 60) + given_time.second
        return self.time_s

    def time_from_s(self, time_in_s):
        """  Transforms the given time in seconds back to the format hh:mm:ss """
        self.time_s = time_in_s
        hours, rest = divmod(self.time_s, 3600)
        minutes, seconds = divmod(rest, 60)
        return ("{0:02d}:{1:02d}:{2:02d}".format(hours, minutes, seconds))

    def exit_program(self, *event):
        """ Close the window and exit the program """
        self.master.destroy()

    def myquit(self):
        """ Messagebox Ask ok to cancel """
        if msgbox.askokcancel("Quit", "Do you really want to quit?"):
            self.exit_program()

    def showInfo(self):
        """ Shows an Info box """
        msgbox.showinfo(
                "Info",
                "Erstellt von sepplx123 \n\n"
                "Orginial idea from: \n"
                "http://www.bartelmus.org/binar-uhr/#more-136"
        )


class Mover():
    """  This are moving objects to show the actual time """

    def __init__(self, canvas, radius1, radius2, start, extent, angle_offset, offsetx, offsety, color1, color2, width):
        self.movers = []
        self.ValueDict = {5: 2 ** 0, 4: 2 ** 4, 3: 2 ** 7, 2: 2 ** 9, 1: 2 ** 10}
        self.canvas = canvas
        self.angle_offset = angle_offset
        self.offsetx = offsetx
        self.offsety = offsety
        self.r1 = radius1
        self.r2 = radius2
        self.start = start
        self.extent = extent
        self.mov_val = 0
        self.direction = 0
        self.range_ = 1000
        limit = 1000

        for i in range(self.range_):
            angle = self.start + i * self.extent / self.range_
            color = color_mix(canvas=self.canvas, limit=limit, color1=color1, color2=color2, i=i)
            x1, y1 = conv_pol_to_kart_coords(self.r1, angle, self.angle_offset, self.offsetx, self.offsety)
            x2, y2 = conv_pol_to_kart_coords(self.r2, angle, self.angle_offset, self.offsetx, self.offsety)
            item = canvas.create_line(x1, y1, x2, y2, fill=color, width=width, tags="mover")
            self.movers.append(item)
            # self.canvas.lower(item)

    def rotate(self, ident):
        """ The amount for the rotation will be calculated according the given ident """
        increment = 360 / 86400 * self.ValueDict[ident]
        self.direction = -1
        self.move(self.mov_val, self.direction)

        if self.mov_val >= 360.0:
            self.mov_val -= 360.0
        self.mov_val += increment

    def show_time(self, time_in_s, ident):
        """
        Mover position corresponding to the actual time
        self.mover1_value = 360/86400*1*self.time_in_s
        self.mover2_value = 360/86400*16*self.time_in_s
        self.mover3_value = 360/86400*128*self.time_in_s
        self.mover4_value = 360/86400*512*self.time_in_s
        self.mover5_value = 360/86400*1024*self.time_in_s
        calc_position = (360 / 86400 * self.moverX_value * self.time_in_s))
        """
        calc_position = 360 / 86400 * self.ValueDict[ident] * time_in_s
        n = int(calc_position) // 360
        calc_position -= n * 360
        self.direction = -1
        self.move(calc_position, self.direction)

    def move(self, value, direction):
        """ Moves the item according to the given value """
        for item in range(len(self.movers)):
            angle = self.start + item * self.extent / self.range_ + direction * value
            x1, y1 = conv_pol_to_kart_coords(self.r1, angle, self.angle_offset, self.offsetx, self.offsety)
            x2, y2 = conv_pol_to_kart_coords(self.r2, angle, self.angle_offset, self.offsetx, self.offsety)
            self.canvas.coords(self.movers[item], x1, y1, x2, y2)

    def reset(self):
        """  reset to start position with angle 0 """
        self.mov_val = 0
        self.direction = 1
        self.move(self.mov_val, self.direction)


class Pulsar():
    """
    This is the Pulsar shown in the middle of the window.
    It represents the micro seconds according to its size.
    1000000 micros represent the biggest size and 0 the minimum with an radius of 0.
    """

    def __init__(self, canvas, x, y, r, offsetx, offsety, color1, color2, width):
        self.pulsar = []
        self.canvas = canvas
        self.radius = r
        limit = r

        for i in range(1, self.radius + 1):
            step = self.radius - i
            color = color_mix(canvas=self.canvas, limit=limit, color1=color1, color2=color2, i=i)
            item = self.canvas.create_oval(offsetx - step, offsety - step, offsetx + step, offsety + step,
                                           outline=color,
                                           fill='', width=width, tags=('pulsar', 'puls_' + str(step)))
            # self.canvas.lift(item)
            self.pulsar.append(item)

    def puls(self, micros):
        """ adjusts the sizes of the object according to the given value """
        self.micros_len_pointer = int(round(micros / 1000000 * self.radius))

        for i in range(len(self.pulsar)):
            if i < self.micros_len_pointer:
                self.canvas.itemconfigure('puls_' + str(i), state='normal')
            else:
                self.canvas.itemconfigure('puls_' + str(i), state='hidden')

    def reset(self):
        """  reset to the init position. Show all items """
        self.canvas.itemconfigure('pulsar', state='normal')


def main():
    """  main program """
    root = tk.Tk()
    ui = GUI(root)
    root.mainloop()


if __name__ == '__main__':
    main()
Antworten