Checkboxen generieren und abfragen

Fragen zu Tkinter.
Antworten
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

Hallo zusammen,

ich erstelle aktuell ein Gui, mit dem ich zuerst mit Hilfe einer SQL Abfrage eine Liste von IDs in eine Combobox packe. Das funktioniert soweit, dass ich auch die Auswahl für die nächste Abfrage übernehmen kann.
Nun möchte ich eine Aufzählung der Inhalte der Tabelle mit einer Checkbox machen, die zu Beginn immer ausgewählt ist. Ich kann die Checkboxen mit einer Schleife erzeugen, da es ja immer unterschiedlich viele sind. Das klappt auch, aber ich bin nicht sicher ob dies der richtige Weg ist, da ich im Anschluss eine weitere Abfrage machen muss, wofür ich jedoch die Information benötige, welche Checkboxen angewählt sind.
Diese Ergebnisse sollen dann in eine fig geplotet werden. hat jemand einen Tipp für mich, wie ich an der Stelle weiter komme?

Danke Euch. :geek:
Benutzeravatar
__blackjack__
User
Beiträge: 13081
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mahera: Ohne zu wissen wo Du an der Stelle *nicht* weiterkommst kann man auch schlecht einen Tipp geben.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

Ich komme nicht weiter mit den Abfragewerten, ob eine Checkbox checked oder unchecked ist. Da ich diese in einer Schleife erstelle und sie daher keinen spezifischen Namen hat.
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Dann tu die Checkboxen doch in eine Liste.
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

import pandas as pd
import tkinter as tk

main =tk.Tk()

def query(question="select * from Tabelle1"):
import pyodbc
conn = pyodbc.connect("DRIVER={{SQL Server}};SERVER={0}; database={1}; \
trusted_connection=yes;UID={2};PWD={3}".format('https','name','user','password'))
df_DUT = pd.read_sql(question, conn)
conn.close()
return df_DUT



o = "SELECT FId, ProId, Test from Tabelle2 where ProId='2903'"
df_help = query(o)
if len(df_help)<2:
print('leer')
h = ['leer']
else:
h = list(df_help['FId'])

b = "SELECT * from Tabelle3 where FId = '" + str(h[0]) + "'" <--- hier möchte ich alle angewählten Checkboxen in einer forschliefe abfragen und ausgeben
dfplott = query(b)
data = query(question=o)
i = 0
calibriations = []
for i in range(len(data)):
text = str(data.iloc[i,0])+" "+str(data.iloc[i,1:])
var = tk.BooleanVar(main)
var.set(True) <-- hier ist bei mir nur die letzte Checkbox true
calibration = tk.Checkbutton(main, text=text , variable=var)
calibration.select()
calibration.grid(row=i+2, column=1 ,sticky=tk.N+tk.W)
calibriations.append(calibration) <-- Hier hänge ich die Checkbox schon in eine liste, aber wie bekomme ich daraus die Werte, die auch bei jedem Klick aktualisiert werden?
i = i+1

main.mainloop()
print('Ende gut, Alles gut')

Vielleich wird mein Problem jetzt klarer.
Benutzeravatar
__blackjack__
User
Beiträge: 13081
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mahera: Importe gehören an den Anfang des Moduls, nicht in Funktionen versteckt.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Da ist es dann verwirrend wenn man das `Tk`-Objekt auch `main` nennt.

Einbuchstabige Namen und kryptische Abkürzungen sollte man vermeiden. `o`, `b`, und `h` gehen gar nicht. Ein Name soll dem Leser vermitteln was der Wert dahinter bedeutet. `i` für eine ganze Zahl die als Index verwendet wird ist okay, weil man das aus der Mathematik kennt.

`df_help` ist zwar nicht zu kurz, aber zu nichtssagend.

Im ``if``-Zweig macht die Zuweisung an `h` keinen Sinn, da der Wert nirgends verwendet wird.

Im ``else``-Zeig wird aus der "FId"-Spalte eine Liste gemacht von der dann aber nur das erste Element im weiteren Verlauf verwendet wird. Da kann man ohne Liste auch einfach das erste Element verwenden.

Es wurde im letzten Thema ja bereits gesagt: Man formatiert keine Werte in SQL als Zeichenketten. Niemals nicht! Das ist gefährlich und potentiell auch ineffizient.

Der Kommentar bei der Definition von `b` ist komisch denn an der Stelle im Programmablauf hat der Benutzer ja noch gar keine Gelegenheit gehabt irgendwelche Checkbuttons an- oder abzuwählen.

`dfplott` wird nirgends verwendet, womit `b` nicht wirklich verwendet wird, womit `h` auch nicht wirklich verwendet wird.

Dann wird `data` mit der gleichen SQL-Abfrage erstellt wie auch schon `df_help` erstellt wurde. Vorausgesetzt zwischen den beiden Abfragen hat sich in der Datenbank nichts geändert, enthalten die also die gleichen Daten. Warum dann zwei Abfragen?

Die Initialisierung von `i` vor der Schleife ist genau so überflüssig wie das ``i = i+1`` am Ende der Schleife weil beide Werte nie irgendwo verwendet werden.

``for i in range(len(sequence)):`` ist in Python ein „anti pattern“. `DataFrame`-Objekte sind zwar in dem Sinne kein Sequenz-Objekt weil `len()` sich auf etwas anderes bezieht als das was über Indexzugriff und Iteration über das Objekt erreichbar ist, aber auch hier ist diese Konstruktion der falsche weg. Es gibt die `iterrows()`-Methode dafür.

Zeichenketten und Werte mit `str()` und ``+`` zusammenstückeln ist eher BASIC als Python. Du kennst doch bereits die `format()`-Methode. Und ab Python 3.6 gibt es f-Zeichenkettenliterale.

`Variable`-Objekten braucht man kein Master-Widget mitgeben, man kann ihnen aber gleich beim Erstellen einen Wert mitgeben und braucht den nicht danach setzen.

Und diese Objekte sind auch das was Du aufheben musst, denn da kann man ja abfragen was der Zustand ist. Im Gegensatz zu den `Checkbutton`-Objekten.

Für Kombinationen von den ”Himmelsrichtungen” hat `tkinter` auch Konstanten, man muss die also nicht einzeln mit ``+`` angeben sondern kann auch einfach beispielsweise `tk.NW` verwenden.

Eine SQL-Abfrage `question` zu nennen ist ungewöhnlich. Defaultwerte sollte man auch nur angeben wenn die tatsächlich allgemein Sinn machen.

Das Formatieren der Zugangsdaten in die ODBC-Verbindungs-Zeichenkette macht IMHO keinen grossen Sinn. Es ist ja gerade praktisch das man alle Daten in *einer* Zeichenkette haben kann, die man beispielsweise auch aus einer Konfigurationsdatei lesen könnte. Oder zumindest als Konstante definieren, damit man nicht im ganzen Programm nach Zugangsdaten suchen muss.

Was zum Henker bedeutet `df_DUT`? `pyodbc.Connection`-Objekte sind Kontextmanager, da braucht man dann also gar keinen Namen für dieses Zwischenergebnis. Und die `query`-Funktion wird auch sehr kurz.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

import pandas as pd
import pyodbc

ODBC_CONNECTION_STRING = (
    "DRIVER={SQL Server};"
    "SERVER=https;"
    "database=name;"
    "trusted_connection=yes;"
    "UID=user;"
    "PWD=password"
)


def query(sql):
    with pyodbc.connect(ODBC_CONNECTION_STRING) as connection:
        return pd.read_sql_query(sql, connection)


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

    products = query(
        "SELECT FId, ProId, Test from Tabelle2 where ProId='2903'"
    )
    if len(products) <= 1:
        print("leer")
    else:
        calibriation_variables = []
        for i, row in products.iterrows():
            variable = tk.BooleanVar(value=True)
            calibriation_variables.append(variable)
            calibration = tk.Checkbutton(
                root, text=f"{row[0]}    {row[1:]}", variable=variable
            )
            calibration.select()
            calibration.grid(row=i, column=1, sticky=tk.NW)

    root.mainloop()
    print("Ende gut, Alles gut")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

__blackjack__ hat geschrieben: Dienstag 25. Februar 2020, 12:08
Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

import pandas as pd
import pyodbc

ODBC_CONNECTION_STRING = (
    "DRIVER={SQL Server};"
    "SERVER=https;"
    "database=name;"
    "trusted_connection=yes;"
    "UID=user;"
    "PWD=password"
)


def query(sql):
    with pyodbc.connect(ODBC_CONNECTION_STRING) as connection:
        return pd.read_sql_query(sql, connection)


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

    products = query(
        "SELECT FId, ProId, Test from Tabelle2 where ProId='2903'"
    )
    if len(products) <= 1:
        print("leer")
    else:
        calibriation_variables = []
        for i, row in products.iterrows():
            variable = tk.BooleanVar(value=True)
            calibriation_variables.append(variable)
            calibration = tk.Checkbutton(
                root, text=f"{row[0]}    {row[1:]}", variable=variable
            )
            calibration.select()
            calibration.grid(row=i, column=1, sticky=tk.NW)

    root.mainloop()
    print("Ende gut, Alles gut")


if __name__ == "__main__":
    main()
Hallo blackjack,

vielen Dank für die ausführlich Antwort und die hilfreichen Tips. ich bin leider noch nicht der erfahrenste Programmierer und verwende gerne mal nichtssagende Buchstaben für Tests. Ich werde das ganze gleich mal testen. Aber Zuvor möchte ich noch was anfügen, was Du vl nicht gesehen oder ich mich zu unklar ausgedrückt habe.

ProId='2903' ist eine Variable nicht eine Konstante. hier wird über eine Combobox, die auf einer SQL Abfrage beruht eine ProId ausgewählt und als variable in die nächste Abfrage implementiert, damit ich nur Kalibrierungen von diesem ausgewählten Gerät sehe.

h = list(df_help['FId']) benötige ich, weil ich über die in dieser Liste aufgeführten Nummern (wo ich jedoch zuvor wissen muss welche ausgewält sind, alle nicht ausgewählten werden nicht benötigt).
Mit b = "SELECT * from Tabele3 where FId = '" + str(h[0]) + "'" und dfplott = query(b) möchte ich nun alle (nicht wie hier im Beispiel nur die erste) FId s der checked Checkboxes verwenden um eine weitere Abfrage zu gestalten, deren Ergebnisse in n-Datenframes sind, welche ich dann ploten kann und später noch diverse statistische Auswertung machen kann.

Da ich noch nicht der geübteste Programmierer bin versuche ich manche Dinge ersteinmal statisch zu gestalten mit der Möglichkeit zur Diversifikation, bevor ich die nächste Hürde nehme.

Vielen Dank aber für alle Tipps.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Man formatiert keine Variablen in SQL-Statements, dafür gibt es Platzhalter.
Und was Du mit dfplott (ein schecklicher Variablenname) machen willst, weiß keiner, also hat __blackjack__ den unnötigen Ballast weggelassen. Wenn Du den brauchst, dann mußt Du den Code halt entsprechend erweitern.

Der Teil ganz sollte dann so aussehen:

Code: Alles auswählen

def execute_query(sql, params=[]):
    with pyodbc.connect(ODBC_CONNECTION_STRING) as connection:
        return pd.read_sql_query(sql, connection, params=params)

query = "SELECT * from Tabelle3 where FId = ?"
dataframe_zum_plotten_von_irgendwas = execute_query(query, [fids[0]])
Die Datenbank ist übrigens auch schrecklich. Tabelle2 oder Tabelle3 sind schon ganz schlechte Namen für Tabellen, weil sie nichts über den Inhalt aussagen. Die Abkürzungen in den Feldnamen sind auch alle nicht gut, was soll denn das F bei FId oder das Pro bei ProID bedeuten?
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

Sirius3 hat geschrieben: Dienstag 25. Februar 2020, 13:57 Man formatiert keine Variablen in SQL-Statements, dafür gibt es Platzhalter.
Habe ich noch nicht gemacht, muss ich mal nachlesen. Danke für den Tipp.
Sirius3 hat geschrieben: Dienstag 25. Februar 2020, 13:57 Und was Du mit dfplott (ein schecklicher Variablenname) machen willst, weiß keiner, also hat __blackjack__ den unnötigen Ballast weggelassen. Wenn Du den brauchst, dann mußt Du den Code halt entsprechend erweitern.
Hatte das aber im Text beschrieben.

Das heisst eigentlich brauche ich eine Lösung, wie ich aus dem Array/ Liste den Wert, true oder false der Checkbox auslesen kann und der mit einem action event verknüpft ist und sich anpasst, sobald man den Status ändert.

Ich wäre froh, wenn mir jemand an dem Punkt helfen könnte.
Benutzeravatar
__blackjack__
User
Beiträge: 13081
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mahera: Ich verstehe nicht so wirklich was Du da machen willst. Aber ich sehe da keine Klassen, was ich schon mal für einen Fehler halte. Und diese Vermischung von Programmlogik und GUI ist auch unschön. Sauberer wäre es Funktionen/Klassen für die Programmlogik zu schreiben und die zu testen, und wenn das läuft, *dann* eine GUI drauf zu setzen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

Hallo Blackjack,

Der Programmablauf sieht insgeamt so aus:

1. Ein Benutzer gibt seine Daten ein über Entry.
2. Es wird die Liste der vorhandenen Devices abgerufen (Combobox, event gibt den actuell gewählten device aus)
3. Durch Betätigung des Search Buttons werden in einem Toplevel alle Kalibrierungen des Devices mit Checkbox aufgelistet
4. Ich wähle die Kalibrierungen aus, welche ich für meine Berechnungen und die Plots benutzen möchte (hier habe ich das Problem, dass ich zwar die Checkboxen erstellen kann, aber nicht weiss wie ich die List auf die Checkboxen mit Wert True kürzen kann, das sollte auch dynamisch sein, also eventbezogen).
5. Jeder Kalibrierung ist eine FId zugordnet (die Nummer der Kalibrierung) diese bekomme ich von den Checkboxen und möchte sie für eine Abfrage, welche mir die Basis für die Berechnungen und Plots liefert. (Dieser Teil fehlt mir noch, ich habe versucht die mit der h[0] Abfrage zu starten, musste aber feststellen, dass ich eigentlich erst das andere Problem lösen sollte)
6. Der Inhalt des Toplevels mit den Berechnungsergebnissen und den Plots soll als PDF gespeichert werden.(Damit habe ich noch nicht angefangen)

Ich habe das ganze in Klassen verpackt, aber es geht mir darum für mein Problem eine Lösung zu finden und da ist es meistens hilfreicher, wenn ich nicht 500 Zeilen Code poste. Zumindest geht es mir so.
Benutzeravatar
__blackjack__
User
Beiträge: 13081
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mahera: Welche Checkboxen ausgewählt wurden kann man ja über die `BooleanVar`-Objekte in der Liste abfragen. Wobei man da dann einen Index mitzählen muss denn mehr Info als `True`/`False` bekommt man aus einem `BooleanVar` ja nicht heraus.

Ich würde wahrscheinlich nicht nur eine Liste mit `tk.Variable`-Objekten nehmen sondern auch noch ein Wörterbuch das Zeichenketten von den IDs die da zur Auswahl stehen auf die auszuwählenden Objekte abbildet und dann nicht `BooleanVar` sondern `StringVar` verwenden und bei den `Checkbutton`\s `onvalue` auf die zugehörige ID und `offvalue` auf die leere Zeichenkette setzen. Dann kann man hinterher einfach die Objekte über dieses Wörterbuch und die `StringVar`-Objekte die etwas anderes als die leere Zeichenkette enthalten ermitteln.

Ich weiss nicht ob das wirklich so günstig ist für alles `DataFrame`\s zu verwenden. Das macht Sinn wenn es viele Daten sind und/oder wo man tatsächlich etwas sinnvolles mit dem `DataFrame` macht. Also beispielsweise plotten. Ansonsten wäre `DataFrame.itertuples()` wahrscheinlich die sinnvollste Variante über Datensätze aus der Abfrage zu iterieren, allerdings wären da dann auch gute Spaltennamen in der Datenbank die den Python-Namenskonventionen für Attribute entsprechen besser geeignet. Sonst würde ich SQLAlchemy und dessen ORM verwenden. SQLAlchemy würde ich mir sowieso an Bord holen weil Pandas' `read_sql_query()`-Funktion laut deren Dokumentation eigentlich nur SQLite3-Connection-Objekte unterstützt. Das `pyodbc.Connection`-Objekte funktionieren ist Zufall, kann also auch Probleme machen oder gar nicht funktionieren. Hier insbesondere der letzte Satz:

Code: Alles auswählen

con : SQLAlchemy connectable(engine/connection), database string URI,
    or sqlite3 DBAPI2 connection
    Using SQLAlchemy makes it possible to use any DB supported by that
    library.
    If a DBAPI2 object, only sqlite3 is supported.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Mahera
User
Beiträge: 9
Registriert: Montag 6. Januar 2020, 12:52

Hi Blackjack,
vielen lieben Dank, das hat mir jetzt sehr viel weitergeholfen.
:idea:
Muss ich gleich testen. Danke Dir.
Antworten