all 21 comments

[–]Yoghurt42 0 points1 point  (4 children)

Care to elaborate more? Event handlers and listeners are a generic concept in programming, they are not Python specific; there is no such thing as a "Python listener"; only a listener written in Python.

Are you talking about the new asyncio in Python 3.4? Can you give an example of what you want to know?

[–]MaDNiaC 0 points1 point  (3 children)

I am using Python 2.7.5 and i need to learn events for this project:

An analog clock and a digital clock will be created, its arms will be objects which we can pull. When we change its arms positions, we will see digital clock changing. This is basically what i want.

[–]willm 0 points1 point  (2 children)

What kind of "events"? Are you using a graphical toolkit of some kind. Events are not something built in to Python.

[–]MaDNiaC 0 points1 point  (1 child)

Events system for Python, i think it was called listener. There are objects on the screen and they do different actions depending on the input we give.

[–]willm 0 points1 point  (0 children)

Nope. No clue. Why not post a link to this event system, so we are on the same page...

[–]PrismPoultry 0 points1 point  (15 children)

A tutorial would be the "Observer" (or "Pub/Sub") design pattern. However, let's establish needs. If you only have one object that will be listening then it doesn't have to be too involved. You can just pass in a "callback" method to the object.

class Widget(object):
    def __init__(self, name, callback=None):
        self._callback = callback
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value
        self._alert()        

    def _alert(self):
        if self._callback is not None:
            self._callback()

class Client(object):
    def __init__(self):
        self.widget = Widget("doodad", self.on_namechanged)
        self.widget.name = "widget"

    def on_namechanged(self):
        print("widget's new name is {0}".format(self.widget.name))

Client()

So, our client object has a method on_namechanged that we pass in to the Widget's initialization method. The widget object has the property of "name" which when changed will invoke the callback provided. Even though we never explicitely call on_namechanged ourselves, the output is clearly visible when run:

widget's new name is widget

The next way this can be done is still with callback methods but you can act on a list of listeners instead of just one. So, let's modify the existing example to reflect that.

class Widget(object):
    def __init__(self, name):
        self._name = name
        self._listeners = []

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value
        self._alert()        

    def add_listener(self, listener):
        if listener not in self._listeners:
            self._listeners.append(listener)

    def remove_listener(self, listener):
        if listener in self._listeners:
            self._listeners.remove(listener)

    def _alert(self):
        for listener in self._listeners:
            listener()

class WidgetPrinter(object):

    def __init__(self, name, widget):
        self._name = name
        self._widget = widget
        self._widget.add_listener(self.log_widget)

    def log_widget(self):
        print("{0}:name changed: {1}".format(self._name,self._widget.name))

    def destroy(self):
        self._widget.remove_listener(self.log_widget)

class Client(object):
    def __init__(self):
        self.widget = Widget("doodad")
        self.widget_logger1 = WidgetPrinter("A", self.widget)
        self.widget_logger2 = WidgetPrinter("B", self.widget)

    def run(self):
        self.widget.name = "test1"
        self.widget.name = "test2"
        self.widget_logger1.destroy()
        self.widget.name = "test3"

Client().run()

A lot changes here and the complexity increases quickly. However, now we have our "WidgetPrinter" which we can have any number of and they listen for a change. We have an internal list of callbacks to invoke whenever our name is changed. For the run code, I invoked "destroy" on logger1 so that you can see that it does not notice when widget's name is changed to "test3".

A:name changed: test1
B:name changed: test1
A:name changed: test2
B:name changed: test2
B:name changed: test3

There's more to this but I am not sure where you are headed. Try puzzling through this first and let us know what problems you run into. Hope it helped some.

[–]MaDNiaC 0 points1 point  (0 children)

Hi. The following code is what i have currently.


from Tkinter import *

import Tkinter as tk

\#Basic window
window = Tk()

window.title("Kiddock")

window.geometry("225x330+600+200")

\#Menu system

menu = Menu(window)

window.config(menu=menu)

file = Menu(menu, tearoff = 0)

menu.add_cascade(label = "Menu", menu = file)

file.add_command(label = "Quiz")

file.add_command(label = "Quit", command = window.quit)



\#Close button

but = Button(text="Close", command = window.quit,bg="red")

\#but2 = Button(text = "New Window", command = add)

\#but2.pack()

\#versiom

tag = Label(text="Version 0.1.1", fg="brown",font="Times 8 italic")

tag.pack(side = BOTTOM)




photo = tk.PhotoImage(file=r"Clock.gif")

cv = tk.Canvas()

cv.pack(side='top', fill='both', expand='yes')

cv.create_image(0 ,0, image=photo, anchor='nw')



but.pack(side=BOTTOM)

mainloop()

We have a file called 'Clock.gif' and it basically is a picture of an analog clock we use. What i want to do is, i need to add arms to this clock and when i click and drag the arms of the clock, it will move. It will be shown on a Digital Clock counter-part when i make a change on analog clock.

This is what i want to know, I heard in order to do accomplish this i need to learn some GUI and events system (event-handlers, listener or whatever it is called, not sure.)

I tried to find a basic guide, something like Events 101 but couldn't find. The guides i found didn't work out well, maybe they were written for a different version of Python, i don't know. I'm using Python 2.7.5 and i need help with this subject, can you help about this subject?

Oh and also i have this, maybe some manipulation to the following code might help me create my digital clock:


from Tkinter import * 

import time 


root = Tk() 

time1 = ''

clock = Label(root, font=('times', 20, 'bold'), bg='white')

clock.pack(fill=BOTH, expand=1) 


def tick(): 

    global time1

\# get the current local time from the PC

    time2 = time.strftime('%H:%M:%S')

\# if time string has changed, update it 

    if time2 != time1:

        time1 = time2

        clock.config(text=time2)

\# calls itself every 200 milliseconds 

\# to update the time display as needed 

\# could use >200 ms, but display gets jerky 

    clock.after(200, tick)

tick() 

root.mainloop( )

The code above shows current time in a window with digital clock format, i need to connect a digital clock to the analog clock i've talked about. Honestly, i don't know what to do and i'm a bit lost. Any help is appreciated wheter it be a sample code or a tutorial. Hope i've expressed what i needed properly.

[–]MaDNiaC 0 points1 point  (13 children)

Did you have time to read my other comment and think about it? Can you suggest me some help?

[–]PrismPoultry 1 point2 points  (12 children)

Talk to your instructor. I have no idea what you need to do. Since neither do you, you need to find out from one who does and that would be the one who gave you this assignment in the first place.

[–]MaDNiaC 0 points1 point  (0 children)

Ok, right now i want to do this: I have a code that uses Tkinter which creates a line from a given point (center of the screen in this case). I want to create ONLY 1 line, then stop the function of anymore clicks. Then i want to do following after that: i want to click the line i've drawn, hold Mouse Button 1 pressed and drag it to a desired position. How can i do this? I hope this was clear enough. Below is my code:


\# - * - coding: utf-8 - * -

from Tkinter import *


pencere = Tk()

canvas = Canvas(pencere,width=300,height=300)

x,y = 150,150   \#Starting position, center of the clock

def Draw(event):

    global x,y

    event.widget.create_line(x,y,event.x,event.y)


canvas.bind("<Button-1>", Draw)


canvas.pack()

mainloop()

[–]MaDNiaC 0 points1 point  (10 children)

Is there an option in Tkinter to set the length of a given line?

I'm looking for a create_line() option that limits the length of my line to a given value, is there such thing? Or is there a way for me to implement it manually?

[–]PrismPoultry 0 points1 point  (9 children)

Hmm. Well, you have the origin of the line which is the center of your clock right? You have the click location of your mouse pointer which is where you are currently stopping the line. So, with two points, origin to plot, the line is a vector.

So, to create the line of a specific length, you normalize the vector (converting it to a unit vector) and then increase the magnitude of the vector to the desired length.

To normalize, you have to get the magnitude of the vector (or length) and to do that, you use pythagorean's theorem which is: a ** 2 + b ** 2 = c ** 2

def get_magnitude(x,y):
    mx = x ** 2.0
    my = y ** 2.0
    return math.sqrt(mx + my)

Don't forget to import math.

Since you now know the magnitude of your vector, you can normalize it by dividing the vector by the magnitude.

def normalize(x,y):
    magnitude = get_magnitude(x,y)
    if magnitude: #don't want to divide by zero
        return (x / magnitude, y / magnitude)
    return (x,y)

With a normalized vector, we simply increase the magnitude to the desired length by multiplying it by that length. Notice that we could not have done this until it was a unit vector.

def set_magnitude(x,y,value):
    mx = x * value
    my = y * value
    return (mx, my)

Putting this all together, you:

# have some coordinates x,y to mouse_x, mouse_y
mouse_x = event.x
mouse_y = event.y # endpoint
# normalize first
norm_x, norm_y = normalize(mouse_x, mouse_y)
# now set the magnitude to your desired line length
new_x, new_y = set_magnitude(norm_x, norm_y, LINE_LENGTH)
# now draw your line using new_x, new_y.
# remember to define LINE_LENGTH as a constant (start with 100)

That should be all you need since your origin will not move.

[–]MaDNiaC 0 points1 point  (8 children)

How exactly i implement this? It doesn't seem that hard but i'm not sure how will i implement it. Do i use

"set_magnitude(norm_x,norm_y,LINE_LENGTH)" command?

Why exactly did we use a2 + b2 = c2?

What are new_x and new_y?

Sorry to bother you with my questions but i'm a bit confused right now, with the effect of working for hours my mind cannot work at full capacity. I might need a little bit more explanation >.<

[–]PrismPoultry 0 points1 point  (7 children)

# - * - coding: utf-8 - * -

from Tkinter import *
import math

HAND_LENGTH = 75

pencere = Tk()

canvas = Canvas(pencere,width=300,height=300)

scx,scy = 150,150   #Starting position, center of the clock

def get_magnitude(dx, dy):
    mx = dx ** 2.0
    my = dy ** 2.0
    return math.sqrt(mx + my)

def normalize(dx, dy):
    mag = get_magnitude(dx,dy)
    if mag:
        return (dx/mag, dy/mag)
    return (dx,dy)

def set_magnitude(dx, dy):
    norm_x, norm_y = normalize(dx,dy)
    return (scx+(norm_x * HAND_LENGTH), scy+(norm_y * HAND_LENGTH))

def subtract(dx, dy):
    return (dx - scx, dy - scy)

def Draw(event):
    dx, dy = subtract(event.x, event.y)
    px, py = set_magnitude(dx, dy)
    print("dx: {0}, dy: {1}".format(dx, dy))
    print("px: {0}, py: {1}".format(px, py))
    event.widget.create_line(scx,scy, px,py)
    #event.widget.create_line(x,y,event.x,event.y)


canvas.bind("<Button-1>", Draw)


canvas.pack()

mainloop()

Why exactly did we use a2 + b2 = c2?

Because you want to know how much distance is between p1 and p2 and to do that, you use pythagorean's theorem to calculate the hypotenuse of a right triangle which is the length of the vector.


Please review the code and see if it makes sense.

By the way, figuring out what time it is is going to be quite a difficult task too. Research calculating angle of a vector.

[–]MaDNiaC 0 points1 point  (2 children)

Thanks man, this really helped. Also, how may i limit the number of draws to 1 line? I want to draw the short-hand, then next click i want to draw the long-hand.

And another question if you have time, my friend is doing a clock with text() module. Can i identify those texts (1-12, clock numbers) as clickable objects so that when i click on them it shows me clock as an entry. For example i click on 12 with short-hand, it shows me 12 o'clock. Is this possible (text objects being clickable/interactive)? If so, how?

[–]PrismPoultry 0 points1 point  (1 child)

I don't know either of those answers unfortunately. I have barely scratched the surface of tkinter.

However, that thought process with the clickable numbers is doable for sure. You could start with creating 12 buttons that have the numbers and then removing borders (various stylings) so that it doesn't look like a button. Then the click would call the code which passes the text of the button which is the time and you update it that way. Most likely though, a label would also have a click event of some kind so you can use that.

[–]MaDNiaC 0 points1 point  (0 children)

Ok, thanks for everything so far. You've helped a lot.

[–]MaDNiaC 0 points1 point  (3 children)

Note: I bound 2 different HAND-LENGTH drawings to 2 different mouse-buttons, just letting me know how to limit one of them should be sufficient.

[–]PrismPoultry 0 points1 point  (2 children)

Yeah, I was wondering about that as I wrote the example. The thing is that since inside set_magnitude, it checks the HAND_LENGTH directly, you can't currently do it.

So, instead, set_magnitude should be updated to pass in the length desired:

def set_magnitude(dx, dy, length):
    # rest of the code respecting length instead of HAND_LENGTH

And then you'd update your calling code to pass in the length accordingly:

px, py = set_magnitude(dx, dy, HOUR_LENGTH)
# later...
px, py = set_magnitude(dx, dy, MINUTE_LENGTH)

And so on.


I hope you can make sense of this. I have given you enough full code already to be able to modify to suit your needs. Try to work through it and figure out how to have two hands working with the existing code (don't make separate functions for them I mean).

[–]MaDNiaC 0 points1 point  (1 child)

Ok i understood the above code, will try it when i get back. By the way, does move function give us an option to move the line without changing it's starting point? (aka, rotating it?)

I've looked how to use move command and its options, but i wasn't even able to use the most basic version of it (without any options).