Fenster nur ausblenden statt schließen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Hallo zusammen,

ich hab mir ein kleines Skript gebastelt, welches auf dict.leo.org per HTTP-Request und mittels BeautifulSoup Wörter nachschlägt und in einem kleinen Fenster (PyQt) anzeigt. (Download)

Das Ganze soll es mir ermöglichen, möglichst wenig umständlich - allein durch das Markieren und Programmstart durch Shortcut - Wörter nachzuschlagen. Es leistet soweit auch ganz gute Dienste - ist aber noch optimierungsfähig: Derzeit wird pro Aufruf ein neues Fenster geöffnet. Das ist ok.

Am besten wäre es jedoch, wenn sich das Fenster beim Schließen nur "versteckt" und das Programm im Speicher gehalten wird. Wird dann erneut das Programm ausgeführt, checkt es erst, ob es schon versteckt im Speicher liegt und "zeigt sich", wenn das der Fall ist bzw. startet neu, wenn nicht.

Nur leider weiß ich nicht, wie ich das realisieren kann ...
Hat von euch jemand einen (allgemeinen) Hinweis, wie ich grundsätzlich ansetzen kann? (Auch Kritik/Lob/Anregungen zum Skript sind willkommen.)

Beste Grüße,
Stefan
"My house is dirty - buy me a clean one!" - Krusty the Clown
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich würde ein Skript machen, das einfach im Hindergrund laufen kann, statt da irgendwelche Fenster zu verstecken. Als User will ich dass Programme die ich zumache auch wirklich zugehen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Hmm. Die Frage ist, wie ich das am besten anstellen kann.
Dafür reichen meine Kenntnisse wohl nicht aus.

Das Problem an der jetzigen Sache ist, dass ich das Programm jeweils wieder von neuem aufrufe. Wenn ich dich richtig verstehe, dann meinst du, dass das Programm dauerhaft im Hintergrund läuft und der neuerliche Programmaufruf eigentlich nur so ne Art "in den Vordergrund holen" darstellt, richtig?

Mein Anliegen ist, dass ich maximal ein Fenster für's Wörter-Nachschlagen offen habe. D.h. wenn schon eins offen ist, dann soll nicht noch ein zweites dazu aufgehen bei einem neuerlichen Programmaufruf. Kann ich das so hinbekommen mit einem im Hintergrund laufenden Programm?
"My house is dirty - buy me a clean one!" - Krusty the Clown
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

philonous hat geschrieben:Mein Anliegen ist, dass ich maximal ein Fenster für's Wörter-Nachschlagen offen habe. D.h. wenn schon eins offen ist, dann soll nicht noch ein zweites dazu aufgehen bei einem neuerlichen Programmaufruf. Kann ich das so hinbekommen mit einem im Hintergrund laufenden Programm?
Du guckst einfach (mittels PID-File, such nach PID im Forum) ob bereits eine Kopie deines Programmes läuft und wenn dies der Fall ist, beendest du sofort. Eine sonderlich tolle Lösung ist das ja nicht (weil es mit mehreren Usern auf einem Rechner nicht zurechtkommt), aber es ist recht einfach.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Danke Hyperion und Leonidas für eure Antworten!

An sich soll das Progamm nicht unbedingt einen Abschnitt in der Taskbar Notification Area haben. Da hab ich schon genügend Zeug drin ;)

Auch die Lösung über das PID-file find ich suboptimal. Es sollte im Prinzip so funktionieren, wie z.B. bei verschiedenen KDE-Anwendungen wie etwa Amarok: Wenn man (in der Konsole) ein zweites Mal "amarok" eintippt, so "checkt" Amarok das und fokussiert das bereits geöffnete Fenster.

Genau das sollte meine Anwendung auch machen.

Meine Überlegung war, das vielleicht mittels DCOP zu realisieren: Dann hätte man quasi zwei Teile. Einmal die Anwendung, die als "Server" läuft und die Aufrufe, die quasi nur das als Server laufende Programm adressieren ...
Leider bin ich noch nicht so sonderlich weit gekommen damit (auch weil ich wenig bis keine Doku zu pydcop gefunden hab). Aber was haltet ihr prinzipiell von dieser Idee?
"My house is dirty - buy me a clean one!" - Krusty the Clown
BlackJack

Prinzipiell eine gute Idee, aber vielleicht solltest Du gleich auf DBUS setzen, statt auf DCOP.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

philonous hat geschrieben:Meine Überlegung war, das vielleicht mittels DCOP zu realisieren: Dann hätte man quasi zwei Teile. Einmal die Anwendung, die als "Server" läuft und die Aufrufe, die quasi nur das als Server laufende Programm adressieren ...
Leider bin ich noch nicht so sonderlich weit gekommen damit (auch weil ich wenig bis keine Doku zu pydcop gefunden hab). Aber was haltet ihr prinzipiell von dieser Idee?
Wie BlackJack sagte, sowas ist schlauer mit dbus zu realisieren. Das ist sogar recht gut dokumentiert und vergleichsweise simpel zu realisieren. Du muss dich dann an den Session-Bus binden. Beim Start musst du einfach nur prüfen ob deine Applikation schon an diesem Bus lauscht und wenn ja dann beenden.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Danke für den Hinweis auf Dbus.

Momentan hab ich das Ganze schon so weit, dass ich einmal ein Server-Objekt am laufen habe und dann per Client eine Methode des Servers "calle". Das funktioniert auch ganz gut, solange ich dabei z.B. einen String "client called" serverseitig ausgeben lasse ...

Die Frage ist jetzt, ob es auch möglich ist, eine QApplication auf dem Server zu starten und das Fenster, falls es schon offen ist, wieder in den Vordergrund zu bringen. Angenommen also ich hab den Browser offen und markier mir ein Wort, das ich nachschlagen möchte; dabei ist das Fenster schon offen, wird aber durch das des Browsers verdeckt.

Perfekt wäre, wenn der Server nun durch "Anruf" des Clients nicht nur kein neues Fenster öffnet, sondern das schon existierende Fenster einfach in den Vordergrund bringt.

Muss ich dafür den Window-Manager ansprechen (wenn ja: wie?) oder kann ich das noch per PyQt realisieren (setFocus scheint dafür nicht auszureichen)?

Im Prinzip sollte der Server also dazu da sein, um die Verwaltung dieses einen Fensters zu gewährleisten. Der Client gibt dann nur an, dass jetzt das Fenster gebraucht wird ...
lunar

Ich würde - wie bereits vorgeschlagen - eine DBUS-Server für das Programm starten.

Um das Programm anschließend im Speicher zu halten, bietet sich imho die Klasse QSystemTrayIcon an. Das ermöglicht dem Nutzer die Kontrolle des Hintergrundprozesses. Im QMainWindow solltest du die closeEvent-Methode überschreiben, und dort das CloseEvent ignorieren sowie hide aufrufen. Da Qt Widgets beim Schließen nicht löscht, ist das Widget noch im Speicher und kann mittels setVisible(True) wieder angezeigt werden, wenn der entsprechende DBUS-Aufruf eingeht, oder der User z.B. auf das Trayicon klickt.

Hier ein Beispiel für ein Qt4 Programm, welches DBus und ein Systemtray-Icon nutzt, um nur eine einzige Instanz zu starten.
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Hervorragend :) Der Code ist sehr gut geeignet für meine Zwecke..! Danke dafür, lunar!

Nur leider funktioniert der GUI-Code jetzt nicht mehr. Mein Code ist ja noch für Qt-Versionen < 4.

Da es mir aber ohnehin sinnvoller scheint, das Ganze in Qt4 zu implementieren, wollt ich mich da auch dran machen. Leider musste ich feststellen, dass das mit dem neuen MV-Pattern, das dort bei Listen bzw. bei Ansichten generell verwendet wird, doch gar nicht so einfach ist. Jedenfalls komm ich damit noch nicht klar.

Ich bräuchte wahrscheinlich nur ein recht einfaches Model (es handelt sich um eine zweispaltige Anzeige; einmal Wörter auf Englisch, einmal auf Deutsch). Aber ich bekomm das nicht hin. Entsprechende PyQt-Tutorials (d.h. für Model/View-Sachen) hab ich bisher leider keine finden können.

Wie implementier ich nun dieses - eigentlich doch einfache - Model?
"My house is dirty - buy me a clean one!" - Krusty the Clown
lunar

philonous hat geschrieben:Hervorragend :) Der Code ist sehr gut geeignet für meine Zwecke..! Danke dafür, lunar!

Nur leider funktioniert der GUI-Code jetzt nicht mehr. Mein Code ist ja noch für Qt-Versionen < 4.
Oh, sorry, das wusste ich nicht. Es sollte aber nicht allzu schwer sein, den Code auf Qt 3 rückzuportieren.
Da es mir aber ohnehin sinnvoller scheint, das Ganze in Qt4 zu implementieren, wollt ich mich da auch dran machen. Leider musste ich feststellen, dass das mit dem neuen MV-Pattern, das dort bei Listen bzw. bei Ansichten generell verwendet wird, doch gar nicht so einfach ist. Jedenfalls komm ich damit noch nicht klar.
Eines vorweg: Obwohl es in den meisten Fällen besser ist, das MFC-Framework tatsächlich zu nutzen, bist du in Qt4 durchaus nicht dazu gezwungen. Es existieren auch noch Item-basierte Widgets, die sich genauso verhalten wie die aus Qt 3 bekannten Listen-Widgets. Eine Überblick darüber liefert die Doku.

Falls du dich dagegen für den MFC-Ansatz entschieden hast, was ich begrüße, so findest du hier ein kleines Beispiel für eine Tabelle samt dazugehörigen Model. Ich hoffe, es hilft dir. Falls nicht, hier noch ein paar weiterführende Links zur Dokumentation:

Model View Programming
Model View Subclassing Guide
QAbstractTableModel
QTableView
QHeaderView


Im Übrigen ist es absolut zu empfehlen, Qt4 zu nutzen. Sowohl das Framework als auch die Python-Bindings haben von diesem Versionsprung definitiv profitiert, Qt 4 Programmierung ist nochmals wesentlich cooler als mit Qt 3.

Edit: Es wäre übrigens gut, wenn du deine bisherigen Versuche einfach mal posten würdest, dass würde eine genauere Fehlerdiagnose ermöglichen, und möglicherweise auch noch andere Verbesserungsvorschläge an den Tag bringen.
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Das erste Skript hatte ich oben schon zum Download verlinkt (ich wollte es hier nicht in code-tags posten, damit das Ganze nicht zu lang wird).

So. Jetzt erstmal ein herzliches Danke an lunar! Deine Code-Beispiele, Links und Hilfe im Allgemeinen war für mich äußerst hilfreich! :)

Geh ich richtig in der Annahme, dass ich jedesmal ein neues Model erzeugen muss und dieses dann der View übergebe? Damit bekomme ich es hin - aber schätze, dass das eigentlich eher suboptimal ist, denn so hab ich ja mehrere Models im Speicher, obwohl ich an sich ingesamt nur eins bräuchte.

Ist es sinnvoller das Model zu "bearbeiten", wenn ich die geholten Übersetzungen einfügen will? Das Problem dabei ist, dass mir nicht klar ist, wie das funktioniert ... Zuerst dachte ich, ich bräuchte (in deinem Beispiel) nur die Klassenvariable "translations" verändern, d.h. einen setter dafür einbauen und der Rest würde dann automatisch gerendert - etwa durch Aufruf einer entsprechenden View-render-Funktion. Aber so scheint das leider nicht zu funktionieren.

Der folgende Code dagegen schon (den Konstruktor des Modells hab ich um die Übergabe der "translations" erweitert):

Code: Alles auswählen

 self.tableView.setModel(GermanEnglishModel(self.tableView, data))
Aber da hab ich eben das Problem mehrerer Models - die ich ja eigentlich nicht brauche ...

Wie mach ich denn das Einfügen der Daten in das Model mit entsprechendem Update der View? (Wie es an sich laufen sollte, kannst du in dem oben verlinkten alten Code sehen.)
"My house is dirty - buy me a clean one!" - Krusty the Clown
lunar

philonous hat geschrieben:Das erste Skript hatte ich oben schon zum Download verlinkt (ich wollte es hier nicht in code-tags posten, damit das Ganze nicht zu lang wird).
Das war aber noch das alte Qt3-Programm. Ein Code-Snippet zu deinen Versuchen mit MFC-Programmierung hast du nicht gepostet ;)
Geh ich richtig in der Annahme, dass ich jedesmal ein neues Model erzeugen muss und dieses dann der View übergebe?
Nein, da gehst du falsch ;) Für gleichartige Daten erzeugst du immer nur eine Model-Instanz. Eine neues Model-Objekt benötigst du eigentlich nur, wenn du im View Daten präsentieren willst, die mit dem alten Model nicht abzubilden sind.
Ist es sinnvoller das Model zu "bearbeiten", wenn ich die geholten Übersetzungen einfügen will?
Das Model ist für die Speicherung der Daten zuständig. Deswegen bearbeitest du das Model auch nicht, wenn du neue Übersetzungen holst, sondern das Model selbst sollte im Idealfall die Übersetzungen holen, entweder direkt über urllib, oder wenn du das von Qt4 lösen möchtest, über ein Client-Model.
Das Problem dabei ist, dass mir nicht klar ist, wie das funktioniert ... Zuerst dachte ich, ich bräuchte (in deinem Beispiel) nur die Klassenvariable "translations" verändern, d.h. einen setter dafür einbauen und der Rest würde dann automatisch gerendert - etwa durch Aufruf einer entsprechenden View-render-Funktion. Aber so scheint das leider nicht zu funktionieren.
Natürlich nicht! Wie sollte das auch gehen? Der View kennt die interne Struktur nicht, in der die Daten abgelegt sind, kann also auch keine Änderungen erkennen. Für QAbstractTableModel gilt das gleiche, immerhin implementierst du das Verwalten der Daten ja erst in deiner abgeleiteten Model-Klasse. Folglich musst du dem View sagen, wenn sich die Daten des Models geändert haben. Da du die Änderungen in deinem Fall nicht genau voraussagen kannst, musst du bei Veränderung der Daten, sprich bei neuen Übersetzungen die reset-Methode des Models aufrufen, welche den View dazu veranlasst, die Daten neu abzufragen. Die Alternative wäre, nur die geänderten Indizes dem View bekannt zu machen, dass allerdings ist in deinem Fall nicht möglich.

Zusammenfassung:
Du implementierst die Logik zum Holen neuer Übersetzungen im Model selbst, fügst dem Model eine fetch_translations(self, word) (oder so ähnlich) Methode hinzu, die du an das clicked-Signal das Buttons (oder was auch immer) bindest. Dort holt das Model dann die Übersetzungen, und ruft anschließend reset auf, um den View zu benachrichtigen.

Die Implementierung sei dir als Übung überlassen. Wenn du das bis morgen nicht geschafft hast, darfst du noch mal fragen ;)

Nein, natürlich darfst du mich immer fragen, ich bin ja froh, dass du Qt4 benutzen willst, und nicht wie so viele hier Tk, Gtk oder Wx ;)
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

Ist es sinnvoller das Model zu "bearbeiten", wenn ich die geholten Übersetzungen einfügen will?
Das Model ist für die Speicherung der Daten zuständig. Deswegen bearbeitest du das Model auch nicht, wenn du neue Übersetzungen holst, sondern das Model selbst sollte im Idealfall die Übersetzungen holen, entweder direkt über urllib, oder wenn du das von Qt4 lösen möchtest, über ein Client-Model.
Langsam wird mir die Sache etwas klarer ... ;)
Das Problem dabei ist, dass mir nicht klar ist, wie das funktioniert ... Zuerst dachte ich, ich bräuchte (in deinem Beispiel) nur die Klassenvariable "translations" verändern, d.h. einen setter dafür einbauen und der Rest würde dann automatisch gerendert - etwa durch Aufruf einer entsprechenden View-render-Funktion. Aber so scheint das leider nicht zu funktionieren.
Natürlich nicht! Wie sollte das auch gehen? Der View kennt die interne Struktur nicht, in der die Daten abgelegt sind, kann also auch keine Änderungen erkennen. Für QAbstractTableModel gilt das gleiche, immerhin implementierst du das Verwalten der Daten ja erst in deiner abgeleiteten Model-Klasse. Folglich musst du dem View sagen, wenn sich die Daten des Models geändert haben. Da du die Änderungen in deinem Fall nicht genau voraussagen kannst, musst du bei Veränderung der Daten, sprich bei neuen Übersetzungen die reset-Methode des Models aufrufen, welche den View dazu veranlasst, die Daten neu abzufragen. Die Alternative wäre, nur die geänderten Indizes dem View bekannt zu machen, dass allerdings ist in deinem Fall nicht möglich.
Ok, ok. An sowas in die Richtung dachte ich durchaus. Nur meinte ich, dass die View zu einem "Aktualisieren" angestoßen werden müsste - aber es scheint ja so zu sein, dass das Model selbst rumposaunen kann, dass es sich verändert hat (reset-Methode).
Nein, natürlich darfst du mich immer fragen, ich bin ja froh, dass du Qt4 benutzen willst, und nicht wie so viele hier Tk, Gtk oder Wx ;)
Und ich bin froh, dass mir da in meinen Fragen so kompetent geholfen wird..! ;)

Ich hab jetzt das kleine Progamm - allein hätt ich das allerdings nicht hinbekommen - fast schon ganz nach meinen Vorstellungen zusammengeschustert. Der "Server" läuft im Hintergrund und ich starte jeweils den Client. Das funktioniert wunderbar. Allerdings ist das Problem, dass das Fenster, wenn es schon vorhanden aber überdeckt ist, sich nicht in den Vordergrund schieben kann: es blinkt zwar wie wild in der Fensterleiste unten, aber kommt nicht nach vorn.

Der Code des Clients sieht aus wie folgt:

Code: Alles auswählen

import sys
from traceback import print_exc

import dbus

def main():
    bus = dbus.SessionBus()

    try:
        mainwindow = bus.get_object('lunar.PyQt4DBusTest',
                            '/lunar/PyQt4DBusTest/MainWindow')
        
        mainwindow.show()
        mainwindow.activateWindow()
        mainwindow.fetchWordFromClipboard()

    except dbus.DBusException:
        print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()
Lieg ich wenigstens diesesmal richtig, wenn ich vermute, dass man dazu irgendwie den Windowmanager ansprechen muss? ;)
Oder ist das noch im Rahmen des Qt-Toolkits möglich?

Beste Grüße und schöne Ostern :)
Stefan
lunar

philonous hat geschrieben:Lieg ich wenigstens diesesmal richtig, wenn ich vermute, dass man dazu irgendwie den Windowmanager ansprechen muss? ;)
Ja. Qt4 kann dem Fenstermanager nur "empfehlen", dass er das Fenster in den Vordergrund holt. Allerdings tust du genau das mit activateWindow. Wenn das trotzdem nicht funktioniert, dann liegt das eher an den Einstellungen des Fenstermanagers. KWin z.B. besitzt einer Funktion, um die Aktivierung zu verhindern (Kontrollzentrum -> Arbeitsfläche -> Fenstereigenschaften -> Erweitert -> Vorbeugung gegen unerwünschte Aktivierung). Bei mir steht diese Einstellung auf "normal", so dass das Fenster nicht in der Vordergrund geholt wird, sondern ebenfalls nur in der Taskleiste blinkt. Setze ich die Einstellung auf "keine", dann erscheint das Fenster im Vordergrund.
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

lunar hat geschrieben:Setze ich die Einstellung auf "keine", dann erscheint das Fenster im Vordergrund.
Verdammt. Bei mir funktioniert das auch mit dieser Umstellung nicht. Der einzige Unterschied ist dann, dass es nun auch nicht mehr blinkt ... :roll:
Unter fluxbox dagegen funktioniert es tadellos.

Gibt es eine einfache Möglichkeit direkt den WindowManager anzusprechen - etwa in der Art des Programms wmctrl?
lunar

philonous hat geschrieben:
lunar hat geschrieben:Setze ich die Einstellung auf "keine", dann erscheint das Fenster im Vordergrund.
Verdammt. Bei mir funktioniert das auch mit dieser Umstellung nicht. Der einzige Unterschied ist dann, dass es nun auch nicht mehr blinkt ... :roll:
Seltsam, bei mir geht es dann. Rufst du die activateWindow Methode des MainWidgets auch wirklich auf?
Gibt es eine einfache Möglichkeit direkt den WindowManager anzusprechen - etwa in der Art des Programms wmctrl?
Möglich, allerdings halte ich nicht viel davon, Programme an den Window-Manager zu binden.
Benutzeravatar
philonous
User
Beiträge: 11
Registriert: Mittwoch 19. März 2008, 19:36

lunar hat geschrieben:
philonous hat geschrieben:
lunar hat geschrieben:Setze ich die Einstellung auf "keine", dann erscheint das Fenster im Vordergrund.
Verdammt. Bei mir funktioniert das auch mit dieser Umstellung nicht. Der einzige Unterschied ist dann, dass es nun auch nicht mehr blinkt ... :roll:
Seltsam, bei mir geht es dann. Rufst du die activateWindow Methode des MainWidgets auch wirklich auf?
Ja, tu ich. Unter fluxbox funktioniert es auch einwandfrei. Bei KDE bzw. kwin dagegen wird zwar das kleine Fenster aktiv ("blinken"), allerdings kommt es auch mit den veränderte Einstellungen nicht in der Vordergrund :(
lunar hat geschrieben:
Gibt es eine einfache Möglichkeit direkt den WindowManager anzusprechen - etwa in der Art des Programms wmctrl?
Möglich, allerdings halte ich nicht viel davon, Programme an den Window-Manager zu binden.
Hmm. Für so ganz ideal halt ich das auch nicht - in diesem Fall aber weiß ich nicht recht, wie ich es anders noch lösen kann.
lunar

philonous hat geschrieben:
lunar hat geschrieben:
philonous hat geschrieben:
lunar hat geschrieben:Setze ich die Einstellung auf "keine", dann erscheint das Fenster im Vordergrund.
Verdammt. Bei mir funktioniert das auch mit dieser Umstellung nicht. Der einzige Unterschied ist dann, dass es nun auch nicht mehr blinkt ... :roll:
Seltsam, bei mir geht es dann. Rufst du die activateWindow Methode des MainWidgets auch wirklich auf?
Ja, tu ich. Unter fluxbox funktioniert es auch einwandfrei. Bei KDE bzw. kwin dagegen wird zwar das kleine Fenster aktiv ("blinken"), allerdings kommt es auch mit den veränderte Einstellungen nicht in der Vordergrund :(
Welche KDE Version hast du denn? Bei mir rennt 3.5.9, und das tut prima.
Antworten