Slotmachine/ Zahleneingabe via Pfeiltasten

Fragen zu Tkinter.
Pete
User
Beiträge: 7
Registriert: Freitag 18. März 2011, 00:20

Hallo zusammen,

ich muss ein gut funktionierendes ökonomisches Experiment erweitern, damit wir es in einem Hirnscanner laufen lassen können. Das bringt mit sich, dass die Eingabe von Zahlenwerten ausschließlich über die Pfeiltasten möglich ist. Damit das funktioniert möchten wir eine Art "Slotmachine" implementieren: Es wird eine zufällige ganze Zahl zwischen 10 und 75 gezogen und zunächst ist die erste Stelle der Zahl aktiv. Mit Pfeil hoch/runter lässt sich dann die 10er-Zahl ändern. Mit Pfeil rechts/links zwischen 10er und 1er Block wechseln. Ist man auf dem 1er Block und drückt ein weiteres mal "rechts", so zählt das als Eingabe und die Zahl wird an den Server gesendet.

Bisher sieht es in unserem Client wie folgt aus:

Code: Alles auswählen

 elif data == "@end \n":
            button.config(state = NORMAL)
            entry.destroy()
            entry = Entry(root, width=12, font = ("Helvetica", "20"))
            entry.focus_set()
            entry.insert(0, "$")
            entry.pack()
            id_ = entry.bind('<Return>', lambda _: root.quit())
            root.mainloop()
            entry.unbind("<Return>", id_)
            str = entry.get()
            entry.delete(0, END)
            root.update()
            sock.send(str[:+10] + " \n")
Sprich, wir haben bisher ein entry widget verwendet und die Versuchspersonen ganz normal via Zahlentasten & Enter-Taste das Experiment bedienen lassen. Die Frage ist jetzt, wie ich vorgehen soll, um das auf die Pfeiltasten zu übertragen.
Ich dachte mir, via entry einfach Zufallszahlen einzufügen und zusätzlich ein paar weitere bind-commands zu bilden. Aber irgendwie möchte das nicht so ganz hinhauen. Was meint ihr?

Die besten Grüße
Pete
BlackJack

@Pete: Also ich meine das ist ein ganz ekliger Weg um sich um ereignisbasierte Programmierung zu drücken. In *dem* Stil würde ich das nicht programmieren und auch niemandem empfehlen.
Pete
User
Beiträge: 7
Registriert: Freitag 18. März 2011, 00:20

Hi BlackJack,

vielen Dank für deine schnelle Antwort. Das Ding ist, dass das nur ein kleiner Teil eines deutlich größeren Programms ist (ca. 2,000 Zeilen) und ich mich bei weitem nicht gut genug auskenne, um zu sehen, welche Auswirkungen eine Änderung auf das ganze weitere Programm haben würde. Ich hab keine Ahnung, was ereignisbasierte Programmierung ist, aber wenn du mir ein paar Anhaltspunkte gibst, wie ich das Problem einigermaßen schnell lösen könnte, wäre ich dir sehr dankbar :)

LG
Pete
BlackJack

@Pete: Wenn das ganze Programm so aufgebaut ist, dann kenne ich keine schnelle Lösung, weil man es dann umschreiben müsste, und dazu müsste man es halbwegs vollständig verstanden haben.
Pete
User
Beiträge: 7
Registriert: Freitag 18. März 2011, 00:20

Hmmm, ja so etwas hatte ich schon fast befürchtet. Das ganze Programm umschreiben ist aber zeitlich nicht wirklich realistisch. Gibt es denn keine Möglichkeit, wie man eine im entry widget präsentierte Zahl als tatsächliche Zahl verwenden und deren erste bzw. zweite Ziffer separat markieren & ändern kann?

Danke
Pete
theo.puke
User
Beiträge: 17
Registriert: Samstag 21. Januar 2012, 16:17

1. Du kannst ja versuchen, den Teil in C++/C#/Java etc. zu schreiben, da diese Sprachen NumericUpAndDown Komponenten besitzen, die die Programmierung von so etwas ziemlich erleichtern.
2. Oder du baust noch zwei Buttons (plus und minus) unter jedes Entry und ersetzt dadurch die Pfeiltasten...

Ersteres durfte wahrscheinlich leichter sein...
Bei mir sieht das jetzt so aus:
C#:

Code: Alles auswählen

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        int zehner;
        int Zehner
        { set 
        { zehner = value;} get {return zehner*10;}}

        int Einser;
        public Form1()
        {
            InitializeComponent();
            DisableAllSecond();


        }

        private void button1_Click(object sender, EventArgs e)
        {
            Zehner = (int)numericUpDown1.Value;
            EnableAllSecond();
        }

        private void EnableAllSecond()
        {
            numericUpDown2.Enabled = true;
            button2.Enabled = true;
            label2.Enabled = true;

        }

        private void DisableAllFirst()
        {
            numericUpDown1.Enabled = false;
            button1.Enabled = false;
            label1.Enabled = false;
        }

        private void DisableAllSecond()
        {
            numericUpDown2.Enabled = false;
            button2.Enabled = false;
            label2.Enabled = false;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            DisableAllFirst();
            Einser = (int)numericUpDown2.Value;
            DisableAllSecond();
            label4.Text = (Einser + Zehner).ToString();
        }
Funktioniert perfekt... Nur schade, dass ich keine Bilder hochladen kann ):
lunar

@theo.puke: Mit der Sprache hat das nichts zu tun. Das Problem des OP ist weder das Toolkit, noch die Sprache, sondern die Struktur des existierenden Quelltexts. Der Wechsel der Sprache ist ohnehin keine Lösung, wenn der OP das Programm schon in Python nicht neu schreiben kann.

Dein C#-Beispiel ist ohne die Resourcen-Datei des Designers nutzlos, weil unvollständig. So würde man das auch nicht entwickeln.
theo.puke
User
Beiträge: 17
Registriert: Samstag 21. Januar 2012, 16:17

@ lunar Nein! Der Code im Designer ist nicht so *wichtig*. Der wird in VC# automatisch generiert, wenn du Komponenten auf die Oberfläche ziehst. Deswegen wollte ich ja ein Bild hochladen, das meine Oberfläche zeigt.
Edit
Aber mit dem Sprachenwechsel gebe ich dir recht, das ist sinnlos.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

theo.puke hat geschrieben:@ lunar Nein! Der Code im Designer ist nicht so *wichtig*. Der wird in VC# automatisch generiert, wenn du Komponenten auf die Oberfläche ziehst. Deswegen wollte ich ja ein Bild hochladen, das meine Oberfläche zeigt.
Und wie sollte der OP oder jemand anderes Deinen Code testen? Müssen wir dann also noch selber "die" Komponenten auf die Oberfläche ziehen? So unwichtig ist die Datei also nicht... ;-)

Und wenn Du ein Bild hochladen wolltest, wieso hast Du es nicht getan?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
theo.puke
User
Beiträge: 17
Registriert: Samstag 21. Januar 2012, 16:17

@Hyperion Ja du hast ja Recht, indem du sagst, die Datei ist wichtig. Wie läd man hier Bilder hoch?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

theo.puke hat geschrieben: Wie läd man hier Bilder hoch?
Du kannst sie nicht direkt hier hochladen, sondern nur auf eine externe URL verlinken. Ich benutze dafür ImageShack, es gibt da ja aber dutzende Anbieter von Bilderhostern.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
theo.puke
User
Beiträge: 17
Registriert: Samstag 21. Januar 2012, 16:17

Gut gut... Dann werde ich mich mal auf die Suche machen.
problembär

Pete hat geschrieben:Das bringt mit sich, dass die Eingabe von Zahlenwerten ausschließlich über die Pfeiltasten möglich ist. Damit das funktioniert möchten wir eine Art "Slotmachine" implementieren: Es wird eine zufällige ganze Zahl zwischen 10 und 75 gezogen und zunächst ist die erste Stelle der Zahl aktiv. Mit Pfeil hoch/runter lässt sich dann die 10er-Zahl ändern. Mit Pfeil rechts/links zwischen 10er und 1er Block wechseln. Ist man auf dem 1er Block und drückt ein weiteres mal "rechts", so zählt das als Eingabe und die Zahl wird an den Server gesendet.
"Mach' das nicht, Du alter Drückeberger und wieso schreibst Du überhaupt in Python?"
So'n Blödsinn, was sind das nur wieder für Antworten hier???
Mein Vorschlag wäre ein Scale-Widget, das hat automatisch Support für die Hoch-/Runtertasten:

Code: Alles auswählen

#!/usr/bin/env python
# coding: iso-8859-1

import Tkinter

def theEnd(scale, mw):
    print scale.get()
    mw.destroy()

mw = Tkinter.Tk()
scale = Tkinter.Scale(mw,
                      from_ = 10,
                      to = 75,
                      orient = Tkinter.VERTICAL,
                      label = "Pick a number, then press Return:")
scale.bind('<Return>', lambda x: theEnd(scale, mw))
scale.focus()
scale.set(42)
scale.pack()
mw.mainloop()
Warum soll man das nicht machen, wenn Tkinter dafür schon eine fertige Lösung bietet?
BlackJack

@problembär: Du solltest vielleicht nochmal die Problemstellung und den bereits vorhanden Quelltext, in den die Lösung eingebaut werden soll, betrachten. Deine Lösung ist keine. Jedenfalls nicht für diese Vorgaben.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

theo.puke hat geschrieben:Gut gut... Dann werde ich mich mal auf die Suche machen.
Danke. Wäre Super wenn du uns doch noch ein Bildchen deiner Anwendung zeigen könntest.

Gruß wuf :wink:
Take it easy Mates!
problembär

BlackJack hat geschrieben:@problembär: Du solltest vielleicht nochmal die Problemstellung und den bereits vorhanden Quelltext, in den die Lösung eingebaut werden soll, betrachten. Deine Lösung ist keine. Jedenfalls nicht für diese Vorgaben.
Du meinst wohl die zwei Stellen (für 10er und 1er), bei denen man mit den Pfeiltasten hin- und hersteuern kann. Sehe ich eigentlich keinen Sinn drin, aber na gut, das wäre dann eben z.B. so:

Code: Alles auswählen

#!/usr/bin/env python
# coding: iso-8859-1

import Tkinter as tk
import random

class SlotScale(tk.Scale):

    def __init__(self, nr, master, defaultvalue, label):
        self.nr = nr
        self.master = master
        self.defaultvalue = defaultvalue
        self.label = label
        tk.Scale.__init__(self,
                          self.master,
                          from_ = 0,
                          to    = 9,
                          orient = tk.VERTICAL,
                          label = self.label)
        self.set(self.defaultvalue)
        self.bind('<Left>', self.master.changeToLeft)
        self.bind('<Right>', self.master.changeToRight)
        self.pack(side = tk.LEFT)

class Slot(tk.Frame):

    def __init__(self, master):
        tk.Frame.__init__(self, master, relief = tk.RIDGE)
        self.master = master
        self.defvalues = self.getDefValues()
        self.scale1 = SlotScale(1, self, self.defvalues[0], "10s")
        self.scale2 = SlotScale(2, self, self.defvalues[1], "1s")
        self.scale1.focus()
        self.focuson = 1
        self.pack(padx = 20, pady = 20)

    def getDefValues(self):
        r = random.randrange(75) + 1
        return (r // 10, r % 10)

    def changeToLeft(self, event):
        if self.focuson == 2:
            self.scale1.focus()
            self.focuson = 1

    def changeToRight(self, event):
        if self.focuson == 1:
            self.scale2.focus()
            self.focuson = 2
        else:
            self.process()

    def process(self):
        print self.scale1.get() * 10 + self.scale2.get()
        self.master.destroy()

mw = tk.Tk()
mw.bind_class("Scale", "<Left>", lambda e: None)
mw.bind_class("Scale", "<Right>", lambda e: None)
slot = Slot(mw)
mw.mainloop()
Das ".bind_class()" an der Stelle ist leicht unsauber, weil es sich auch auf andere Scale-Widgets auswirkt, die die Anwendung gegebenenfalls haben könnte, siehe den Abschnitt "Instance and Class Bindings" in dem Dokument hier.

Ich würde mich über einen Verbesserungsvorschlag dahingehend freuen, daß nur in dem konkreten "SlotScale"-Widget das zusätzliche Verhalten der Links-/Rechts-Pfeiltasten (Verminderung, bzw. Erhöhung des Scale-Wertes) ausgeschlossen wird, also jeweils nur die ".changeToLeft()", bzw. ".changeToRight"-Methode des übergeordneten "Slot"-Widgets aufgerufen wird, wie es jetzt schon - wie gesagt leicht unsauber - realisiert ist.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

theo.puke hat geschrieben:Du kannst ja versuchen, den Teil in C++/C#/Java etc. zu schreiben,
Ich beherrsche diese Sprachen leider nicht. Habe einmal versucht nach den Vorgaben von 'Pete', die noch nicht präzise genug sind, etwas in Tkinter & Python zusammen zu basteln:

Bild

Skript ausgelagert!

Gruß wuf :wink:
Zuletzt geändert von wuf am Freitag 10. Februar 2012, 01:14, insgesamt 1-mal geändert.
Take it easy Mates!
problembär

@wuf: Hab' kein functools (Py 2.4), geht aber auch ohne, mit der Änderung zu:

Code: Alles auswählen

button_up = tk.Button(left_frame,
            text=BUT_UP, command = lambda arg = BUT_UP: self.callback(BUT_UP))
        button_up.pack(fill='both')
Jedoch: Leider erhalte ich bei Dir keine Reaktion mit den Pfeiltasten. :(
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

problembär hat geschrieben:@wuf: Hab' kein functools (Py 2.4), ...
Dann solltest Du aber wirklich mal auf 2.7 updaten ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
problembär

@Hyperion: Hab' ich mal versucht, hatte unerwartete Effekte: "Never change a running system." ;)
-----
Man braucht noch nicht mal Scale, es geht sogar mit einem einfachen Label. Ich finde es so eigentlich sogar noch eleganter:

Code: Alles auswählen

#!/usr/bin/env python
# coding: iso-8859-1

import Tkinter as tk
import random

class SlotLabel(tk.Label):

    def __init__(self, master, defaultvalue):
        self.master = master
        self.value = defaultvalue
        self.var = tk.Variable()
        self.var.set(str(self.value))
        tk.Label.__init__(self,
                          self.master,
                          font = "Arial 22",
                          fg = 'black',
                          bg = 'white',
                          borderwidth = 3,
                          textvariable = self.var)
        self.bind('<Up>', self.increaseValue)
        self.bind('<Down>', self.decreaseValue)
        self.bind('<Left>', self.master.changeToLeft)
        self.bind('<Right>', self.master.changeToRight)
        self.pack(side = tk.LEFT, padx = 5)

    def increaseValue(self, event):
        if self.value < 9:
            self.value += 1
            self.var.set(str(self.value))

    def decreaseValue(self, event):
        if self.value > 0:
            self.value -= 1
            self.var.set(str(self.value))

    def enableFocusRidge(self):
        self.configure(relief = tk.SOLID)

    def disableFocusRidge(self):
        self.configure(relief = tk.FLAT)

class Slot(tk.Frame):

    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.master = master
        self.defvalues = self.getDefValues()
        self.label1 = SlotLabel(self, self.defvalues[0])
        self.label2 = SlotLabel(self, self.defvalues[1])
        self.label1.focus()
        self.label1.enableFocusRidge()
        self.focuson = 1
        self.pack(padx = 20, pady = 20)

    def getDefValues(self):
        r = random.randrange(75) + 1
        return (r // 10, r % 10)

    def changeToLeft(self, event):
        if self.focuson == 2:
            self.label1.enableFocusRidge()
            self.label2.disableFocusRidge()
            self.label1.focus()
            self.focuson = 1

    def changeToRight(self, event):
        if self.focuson == 1:
            self.label1.disableFocusRidge()
            self.label2.enableFocusRidge()
            self.label2.focus()
            self.focuson = 2
        else:
            self.process()

    def process(self):
        print int(self.label1.var.get()) * 10 + int(self.label2.var.get())
        self.master.destroy()

mw = tk.Tk()
slot = Slot(mw)
mw.mainloop()
Antworten