GTK Vew und Code trennen - Links Infos und Tutorials gesucht

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
Benutzeravatar
martinjo
User
Beiträge: 174
Registriert: Dienstag 14. Juni 2011, 20:03

Freitag 13. Mai 2016, 13:53

Hallo

Ich habe diverse kleine Projekte die ich mit GTK2 (pygtk Modul) angefangen habe. Bei diesen habe ich immer View und Code in einem Skript.


Nun habe ich schon oft gehört, dass man die Sicht und den Code trennen soll. Das würde ich gerne umsetzen und freue mich wenn jemand hierzu ein paar Links, Infos oder Tutorials empfehlen kann. Gleichzeitig werde ich dann einen Umstieg auf GTK3 wagen (gi.repository Modul))

Hier noch ein Beispiel eines der Skripte, ein kleines Fenster dass Paket-Sendungen aus einer Datenbank ausliest.

Code: Alles auswählen

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

"""
sudo apt-get install python-keyring (Version >= 4 )

echo - C '''[Desktop Entry]
Comment=
Terminal=True
Name=Shipments GTK
Exec=python shipments_gtk.py
Type=Application
Icon=''' > ~/.local/share/applications/shipment_search_gtk.desktop

19.04.2016
"""

import os
import sys
import gtk
import csv
import time
import datetime
import logging
from import_2_db import load_from_mail_archives
from sqlite3 import dbapi2 as sqlite

## LOGGING
logger = logging.getLogger("ShipmentsGTK")
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s(%(lineno)s) - %(levelname)s: %(message)s", datefmt='%Y.%m.%d %H:%M:%S')
logger.info("starting script ShipmentsGTK")
logger.info("Path of script: %s", (os.path.abspath(sys.argv[0]))

## CONFIG
DATABASE = os.path.join (os.path.expanduser('~'), "TOOLS/data/intraship")


class ShipmentsGTK:

    def destroy(self, widget, data=None ):
        logger.debug("run destroy")
        gtk.main_quit()

    # visibility determined by name
    def visible_func(self, model, iter, data=None):
        if self.searchbox.get_text():
            query = self.searchbox.get_text().lower()
            values = []
            for n in [0, 2, 3, 4, 6, 7]:
                if model.get_value(iter, n):
                    values.append(model.get_value(iter, n))
            values = " ".join(values)
                
            if query in values.lower():
                return True
            else:
                return False
        else:
            return True


    # filter function
    def on_entry_refilter(self, widget):
        logger.debug("run on_entry_refilter")
        self.modelfilter = self.liststore.filter_new()
        self.treeview.set_model(self.modelfilter)
        self.modelfilter.set_visible_func(self.visible_func, self.searchable_row_content)
        self.modelfilter.refilter()


    # download new data
    def download(self, widget):
        logger.debug("run download")
        load_from_mail_archives()
        base.load_from_db()

    
    def onSelectionChanged(self, widget, data=None):
        logger.debug("run onSelectionChanged")
        selection = self.treeview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        selected_tree_model, selected_tree_iter = selection.get_selected()
        if selected_tree_iter:
            self.selected_info_shipping_number.set_text(selected_tree_model.get_value(selected_tree_iter, 0) )
            self.selected_info_date.set_text(selected_tree_model.get_value(selected_tree_iter, 1) )
            self.selected_info_name.set_text(selected_tree_model.get_value(selected_tree_iter, 2) )
            self.selected_info_street.set_text(selected_tree_model.get_value(selected_tree_iter, 3) )
            self.selected_info_city.set_text(selected_tree_model.get_value(selected_tree_iter, 4) )
            self.selected_info_country.set_text(selected_tree_model.get_value(selected_tree_iter, 5) )
            #self.selected_info_phone.set_text(selected_tree_model.get_value(selected_tree_iter, 6) )
            self.selected_info_reference.set_text(selected_tree_model.get_value(selected_tree_iter, 7) )
            
        
    def load_from_db(self, widget=None):
        logger.debug("run load_from_db")
        logger.debug("connect to database")
        con = sqlite.connect(DATABASE)
        cur = con.cursor()
        cur.execute("SELECT * FROM intraship")
        logger.info("cur.description = %s", cur.description)
        header = [desc[0] for desc in cur.description] 
        logger.info("header = %s", header)
 
        if not self.liststore:
            # create store and treeview
            self.liststore = gtk.ListStore(*[type(col) for col in header])
            self.modelfilter = self.liststore.filter_new()
            self.my_tree_model_sort = gtk.TreeModelSort(self.modelfilter)
            self.treeview = gtk.TreeView(self.my_tree_model_sort)
            self.treeview.connect("cursor-changed", self.onSelectionChanged)
            # fill treeview
            logger.debug("fill treeview")
            for number, name in enumerate(header):
                logger.debug("column %s = %s", number, name)
                cell = gtk.CellRendererText()
                cell.set_property('editable', True)
                column = gtk.TreeViewColumn(name, cell, text=number)
                column.add_attribute(cell, "text", number)
                column.set_sort_column_id(number)
                self.treeview.append_column(column)

            model_tree_sortable = self.treeview.get_model()
            #sort_function =  model_tree_sortable.get_default_sort_func()
            model_tree_sortable.set_default_sort_func(lambda *unused: 0)
    
            self.treeview.set_search_entry(self.searchbox)
            self.main_sw.add_with_viewport(self.treeview)
        logger.debug("clear existing store content")
        self.liststore.clear()
        
        # fill store with content
        self.treeview.freeze_child_notify()
        self.treeview.set_model(None)
        self.searchable_row_content = []

        logger.debug("add content to store")
        n = 0
        for n, line in enumerate(cur):
            line = [l.encode("utf8") for l in line]
            self.liststore.append(line)
            self.searchable_row_content.append((line[2], line[3]))
        self.selected_info_entry.set_text(%s entries", n)     
        
        self.treeview.set_model(self.my_tree_model_sort)
        self.treeview.thaw_child_notify()   
    
        self.window.show_all()


    def __init__(self):
        
        self.liststore = None

        # main window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Intraship")
        self.window.set_position(gtk.WIN_POS_CENTER)
        self.window.set_size_request(1000, 600)
        
        # main box
        self.mainbox = gtk.VBox()
        self.window.add(self.mainbox)

        # search box
        self.searchbox = gtk.Entry()
        self.searchbox.connect("changed", self.on_entry_refilter)
        self.mainbox.pack_start(self.searchbox, expand=False)
        
        # buttons
        self.buttonbox = gtk.HBox()
        self.mainbox.pack_start(self.buttonbox, expand=False)
        self.loadbutton = gtk.Button("Load")
        self.loadbutton.connect("clicked", self.load_from_db)
        self.buttonbox.pack_start(self.loadbutton, expand=False)
        self.downloadbutton = gtk.Button("Download")
        self.downloadbutton.connect("clicked", self.download)
        self.buttonbox.pack_start(self.downloadbutton, expand=False)
        
        # scrolled window box
        self.main_sw = gtk.ScrolledWindow()
        self.mainbox.pack_start(self.main_sw, expand=True)

        # Info Label
        self.selected_info_entry = gtk.Label("Info")
        self.selected_info_entry.set_selectable(1)
        self.selected_info_entry.set_text(str(0)+" entries" )
        self.buttonbox.pack_start(self.selected_info_entry, expand=False)

        ## Selected Info Box
        self.selected_info_box = gtk.HBox()
        self.mainbox.pack_start(self.selected_info_box, expand=False)
        
        # left info box
        self.selected_info_left = gtk.VBox()
        self.selected_info_box.pack_start(self.selected_info_left)
        self.selected_info_shipping_number = gtk.Label("Number")
        self.selected_info_shipping_number.set_selectable(1)
        self.selected_info_left.pack_start(self.selected_info_shipping_number)
        self.selected_info_date = gtk.Label("Date")
        self.selected_info_date.set_selectable(1)
        self.selected_info_left.pack_start(self.selected_info_date)
        self.selected_info_reference = gtk.Label("Reference")
        self.selected_info_reference.set_selectable(1)
        self.selected_info_left.pack_start(self.selected_info_reference)
        
        # middle info box
        self.selected_info_mid = gtk.VBox()
        self.selected_info_box.pack_start(self.selected_info_mid)
        self.selected_info_name = gtk.Label("Name")
        self.selected_info_name.set_selectable(1)
        self.selected_info_mid.pack_start(self.selected_info_name)
        self.selected_info_street = gtk.Label("Street")
        self.selected_info_street.set_selectable(1)
        self.selected_info_mid.pack_start(self.selected_info_street)
        self.selected_info_city = gtk.Label("City")
        self.selected_info_city.set_selectable(1)
        self.selected_info_mid.pack_start(self.selected_info_city)
        self.selected_info_country = gtk.Label("Country")
        self.selected_info_country.set_selectable(1)
        self.selected_info_mid.pack_start(self.selected_info_country)
        
        # right info box
        self.selected_info_right = gtk.VBox()
        self.selected_info_box.pack_start(self.selected_info_right)

        # exit button
        self.exitbutton = gtk.Button(stock=gtk.STOCK_QUIT)
        self.exitbutton.connect("clicked", self.destroy)
        self.mainbox.pack_start(self.exitbutton, expand=False)
        
        # show main window
        self.window.show_all()
        self.window.connect("destroy", self.destroy)

    def main(self):
        gtk.main()


if __name__ == "__main__":
    base = ShipmentsGTK()
    base.load_from_db()
    base.main()
edit:
aus dem Thema "Trennung von Gui und Code" (viewtopic.php?t=21411)
Stichworte: "Model-View-Controller"-Konzept,
Tipps: Eine Klassen für GUI, eine für Funktionen.

In wie fern ist hier gtkmvc zu empfehlen, auch in Hinblick auf einen Wechsel auf Python 3.0 in den nächsten 1-2 Jahren ?
Bisher habe ich separaten XML Dateien vermieden, dies scheint aber wohl doch das Konzept der Zukunft zu sein?
Sirius3
User
Beiträge: 7788
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 13. Mai 2016, 14:52

@martinjo: was man trennen sollte, ist die GUI von der Programmlogik, also dem lösen der eigentlichen Aufgabe. Das hat den Vorteil, dass man diesen Teil automatisiert testen kann, was über eine GUI schlecht bis unmöglich ist. MVC als Konzept ist nett, wenn man mal darüber liest, im Alltag wenig praktikabel. Ob man das jetzt ein einer oder mehreren py-Dateien stehen hat, ist eine reine Geschmacksfrage. Eine Klasse für Funktionen ist unsinnig, weil Klassen Daten kapseln sollten. Wann Klassen sinnvoll sind, kommt dann wieder auf die Anwendung an.

Als Leitlinie solltest Du das Programm ohne GUI von der interaktiven Pythonshell aus bedienen können. Wenn das der Fall ist, hast Du die Trennung geschafft. In den nächsten 1-2 Jahren solltest Du wenn dann auf Python 3.5 oder 3.6 wechseln und nicht auf 3.0.
Antworten