FocusIn, Item-Position in Dataset-Tabelle ermitteln

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

Ich möchte in meiner Dataset-Tabelle, die Item-Position bei FocusIn ermitteln.
Über den eventFilter und QtCore.Qt.StrongFocus habe ich versucht, dies zu bewältigen, komme da auf keinen grünen Zweig.
Ich hoffe, dass Ihr mir dabei helfen könnt, was ich falsch mache bzw. wie es richtig geht.

Meinen Code entsprechend gekürzt, so dass er für Euch lauffähig ist.

Code: Alles auswählen

import os
import sys
import operator  # used for sorting
from PyQt4 import QtCore, QtGui

title = 'Backlight management'
header = ['Pos', 'Supplier', 'Artikel', 'Benennung']
dataset = ['001', 'Meyer', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch']
columnWidths = [20, 15, 20, 40]

class MainWindow(QtGui.QMainWindow):
        
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi_area = QtGui.QMdiArea()
        self.setCentralWidget(self.mdi_area)
        sub = Dataset(title, dataset, header, columnWidths)
        sub.setWindowTitle(title)
        self.mdi_area.addSubWindow(sub)
        sub.show()

class Dataset(QtGui.QWidget): 

    def __init__(self, title, dataset, header, width): 
        super(Dataset, self).__init__()
        self.setWindowTitle(title)
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.width = width
        self.setObjectName('DATASET')
        self.installEventFilter(self)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.view = QtGui.QTableView(self)
        # GridLayout
        grid = QtGui.QGridLayout() 
        grid.addWidget(self.view, 0, 0) 
        self.setLayout(grid)
        self.model = QtGui.QStandardItemModel(self) 
        # Load dataset
        [self.model.invisibleRootItem().appendRow(
            QtGui.QStandardItem(column)) for column in self.dataset]
        # Vertical header
        [self.model.setHeaderData(i, QtCore.Qt.Vertical, column)
            for i, column in enumerate(header)]

        self.proxy = QtGui.QSortFilterProxyModel(self) 
        self.proxy.setSourceModel(self.model)
        self.view.setModel(self.proxy) 

    def eventFilter(self, widget, event):
        """
        View events and check for tasks
        """
        if event.type() == QtCore.QEvent.FocusIn:
            try:
                if self.focusin != (widget, event):
                    self.focusin = widget, event
            except AttributeError:
                self.focusin = widget, event
                for i in range(len(self.header)):
                    item = QtGui.QStandardItem(i)
            return True
        return QtGui.QWidget.eventFilter(self, widget, event)

    def closeEvent(self, event):
        self.exec_()
        event.ignore()
        event.accept()

    def exec_(self):
        self.dataset = [self.model.invisibleRootItem().child(i).text()
            for i in range(len(self.header))]
        print('self.dataset', self.dataset)

def main():

    app = QtGui.QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())
        
if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich versuche das mal ans laufen zu bekommen. Ein Grund warum dem so ist: http://blog.qt.io/blog/2014/11/27/qt-4- ... ther-year/

Qt4 ist wirklich SEHR alt. Und nicht mehr gewartet. Du solltest unbedingt auf Qt5 umziehen. Das sollte nicht ueber die Massen schwer sein.


Edit: ich hab's umgebaut und zum laufen gebracht:

Code: Alles auswählen

import os
import sys

# nur wichtig fuer meine lokale installation
for p in [
        "/usr/local/Cellar/pyqt5/5.10.1_1/lib/python3.7/site-packages",
        "/usr/local/Cellar/sip/4.19.8_6/lib/python3.7/site-packages",
        ]:
    sys.path.append(p)

import operator  # used for sorting
from PyQt5 import QtCore, QtGui, QtWidgets

title = 'Backlight management'
header = ['Pos', 'Supplier', 'Artikel', 'Benennung']
dataset = ['001', 'Meyer', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch']
columnWidths = [20, 15, 20, 40]

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi_area = QtWidgets.QMdiArea()
        self.setCentralWidget(self.mdi_area)
        sub = Dataset(title, dataset, header, columnWidths)
        sub.setWindowTitle(title)
        self.mdi_area.addSubWindow(sub)
        sub.show()

class Dataset(QtWidgets.QWidget):

    def __init__(self, title, dataset, header, width):
        super(Dataset, self).__init__()
        self.setWindowTitle(title)
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.width = width
        self.setObjectName('DATASET')
        self.installEventFilter(self)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.view = QtWidgets.QTableView(self)
        # GridLayout
        grid = QtWidgets.QGridLayout()
        grid.addWidget(self.view, 0, 0)
        self.setLayout(grid)
        self.model = QtGui.QStandardItemModel(self)
        # Load dataset
        [self.model.invisibleRootItem().appendRow(
            QtGui.QStandardItem(column)) for column in self.dataset]
        # Vertical header
        [self.model.setHeaderData(i, QtCore.Qt.Vertical, column)
            for i, column in enumerate(header)]

        self.proxy = QtCore.QSortFilterProxyModel(self)
        self.proxy.setSourceModel(self.model)
        self.view.setModel(self.proxy)

    def eventFilter(self, widget, event):
        """
        View events and check for tasks
        """
        if event.type() == QtCore.QEvent.FocusIn:
            try:
                if self.focusin != (widget, event):
                    self.focusin = widget, event
            except AttributeError:
                self.focusin = widget, event
                for i in range(len(self.header)):
                    item = QtGui.QStandardItem(i)
            return True
        return QtWidgets.QWidget.eventFilter(self, widget, event)

    def closeEvent(self, event):
        self.exec_()
        event.ignore()
        event.accept()

    def exec_(self):
        self.dataset = [self.model.invisibleRootItem().child(i).text()
            for i in range(len(self.header))]
        print('self.dataset', self.dataset)

def main():

    app = QtWidgets.QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
Jetzt habe ich nur keine Ahnung, was du eigentlich willst. Kannst du das mal ausfuehrlicher beschreiben?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hintergrund ist der, dass bei Änderung bei einem beliebigen Item, wo ich nicht aus dem Item herausgehe, die Änderung nicht übernommen wird. Ganz klar man muss aus dem betreffenden Item in ein anderes gehen, damit die Änderung greift.
Manchmal ändert man ein Item, schließt das Dataset ohne das Item zu verlassen und die Änderung wird nicht übernommen. Daher möchte ich mit FocuIn festhalten, welches das active Item ist und beim Beenden des Datasets automatisch zu dem nächsten Item springen, um die Änderung zu erhalten.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Also fuer mich funktioniert das. Ich habe mit deinem Code ein kleines bisschen rumgespielt, aber eigentlich nichts bewegendes geaendert. Der weg wenn waere auch IMHO nicht ueber den Focus, sondern ueber die clicked-action. Wie dem auch sei - wenn man das MDI-Fenster schliesst, und das Modell ausgegeben wird, dann sind da auch gerade geaenderte Daten drin.

Ggf. ein weiterer Grund, zu Qt5 zu wechseln.

Code: Alles auswählen

import os
import sys
for p in [
        "/usr/local/Cellar/pyqt5/5.10.1_1/lib/python3.7/site-packages",
        "/usr/local/Cellar/sip/4.19.8_6/lib/python3.7/site-packages",
        ]:
    sys.path.append(p)

import operator  # used for sorting
from PyQt5 import QtCore, QtGui, QtWidgets

title = 'Backlight management'
header = ['Pos', 'Supplier', 'Artikel', 'Benennung']
dataset = ['001', 'Meyer', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch']
columnWidths = [20, 15, 20, 40]

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, model):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi_area = QtWidgets.QMdiArea()
        self.setCentralWidget(self.mdi_area)
        sub = Dataset(title, dataset, header, columnWidths, model)
        sub.setWindowTitle(title)
        self.mdi_area.addSubWindow(sub)
        sub.show()

class Dataset(QtWidgets.QWidget):

    def __init__(self, title, dataset, header, width, model):
        super(Dataset, self).__init__()
        self.setWindowTitle(title)
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.width = width
        self.setObjectName('DATASET')
        self.installEventFilter(self)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.view = QtWidgets.QTableView(self)
        # GridLayout
        grid = QtWidgets.QGridLayout()
        grid.addWidget(self.view, 0, 0)
        self.view.clicked.connect(self._item_clicked)
        self.setLayout(grid)
        # Load dataset
        [model.invisibleRootItem().appendRow(
            QtGui.QStandardItem(column)) for column in self.dataset]
        # Vertical header
        [model.setHeaderData(i, QtCore.Qt.Vertical, column)
            for i, column in enumerate(header)]

        self.proxy = QtCore.QSortFilterProxyModel(self)
        self.proxy.setSourceModel(model)
        self.view.setModel(self.proxy)
        self.model = model

    def _item_clicked(self, index):
        print(index)


    def closeEvent(self, event):
        self.exec_()
        event.ignore()
        event.accept()

    def exec_(self):
        self.dataset = [self.model.invisibleRootItem().child(i).text()
            for i in range(len(self.header))]
        print('self.dataset', self.dataset)

def main():
    model = QtGui.QStandardItemModel()
    app = QtWidgets.QApplication(sys.argv)
    ex = MainWindow(model)
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Sorry, da habe ich Dich am Anfang falsch verstanden.
Dein Code mit Änderung funktioniert, aber egal welches Item ich klicke, es wird immer das gleiche Item ausgegeben.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da wird kein Item ausgegeben. Da wird ein Index ausgegeben, den du dann mit itemFromIndex auf deinem Modell wieder aufloesen kannst. So weit bin ich gar nicht gekommen, weil es ja schon fuer mich funktioniert hat. Aber damit kommst du an das Item, das dich interessiert.

http://doc.qt.io/qt-5/qstandarditemmode ... mFromIndex

Ob das schlussendlich dein Problem loest, steht allerdings auf einem anderen Blatt. Wie gesagt - ich *habe* dieses Problem gar nicht, und kann dir nochmal nur nachdruecklich empfehlen, Qt5 zu benutzen.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

PyQt5, ist bei mir auch installiert.
Habe auch den Code von Dir am Laufen.
Versuche mich mal bei Deinem Link durch zu arbeiten, werde meine Englisch-Translater verwenden.

Danke Dir vorerst einamal!

Grüße Nobuddy
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Das habe ich bisher herausgefunden:

Code: Alles auswählen


    def _item_clicked(self, index):
        self.control_item_focus(index)

    def control_item_focus(self, index):
        i = index.row()
        try:
            if self.focusin != i:
                self.focusin = i
                print('Focus new', self.focusin)
        except AttributeError:
            self.focusin = i
            print('Focus start', self.focusin)
        print('ttt', index.data(i))
        self.item_widget(i)

    def item_widget(self, i):
        item_widget = self.model.invisibleRootItem().child(i)
        next_i = i+1
        if next_i > len(self.header) - 1:
            next_i = 0
        next_item_widget = self.model.invisibleRootItem().child(next_i)
        print('item_widget', item_widget)
        print('next_item_widget', next_item_widget)
Leider habe ich noch nicht herausgefunden, wie ich den Focus zu nächsten Item bekomme.
Evtl. eine Idee?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist so nicht richtig. Was hindert dich daran, die von mir genannte Methode zu benutzen? Und hast du das mit Qt5 probiert, und verifiziert, dass du dein Problem ueberhaupt noch hast?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

PyQt5 ist installiert und Dein Code habe ich auch übernommen.
Der zuletzt gepostete Code ist ja nur ein Ausschnitt.

Mit 'index.row()', erhalte ich die Position meines Items im Dataset, das habe ich in Deinem Link gefunden.
Danach ermittle ich das Item-Widget und das Folgende, bitte verzeih wenn der Ausdruck falsch sein sollte.
So falsch kann ich doch nicht liegen, oder?
Wenn ja, wo bin ich falsch und wie müsste es sein?

Ich möchte eigentlich erreichen, dass beim Beenden des Datsets, der Focus zum nächsten Item gesetzt wird.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe schon verstanden, was du willst. Dein Vorgehen ist nur umstaendlich und unnoetig. Der von mir gepostete Link wandelt einen Index unmittelbar in das dazugehoerige Item. Damit faellt also dein Code in item_widget weg. Wenn du dann das naechste oder davorliegende Item willst, dann kannst du dir einfach einen dazugehoerigen Index mit "index.sibling(1,0)" oder index.sibling(-1,0) erzeugen.

Dann ist sowas wie in control_item_focus mit dem AttributeError ein absolutes no-no, und du solltest das nicht tun. Speicher statt dem i einfach direkt den Index aus der _item_clicked-Methode, und leg das self.focusin natuerlich im Konstruktor an.

Last but not least ist dein ganzes Vorgehen fragwuerdig: fuer mich kommt es nicht zu dem beschriebenen Verhalten. Hast du das geprueft? Das kann ja durchaus auch ein Bug in Qt4 gewesen sein, der in Qt5 eben gefixt ist. Und WENN es dieses Verhalten gibt (ich seh's nicht), dann ist dein Plan zum scheitern verurteilt, wenn es nur EIN Item gibt. Dann kannst du auch kein anderes in den Fokus bringen.

Das waere dann wenn das explizite fokussieren eines anderen widgets eine Moeglickheit. Es geht ja nicht darum, ein anderes Item zu fokussieren. Sondern dem aktuellen den Fokus zu *entziehen*, wohin auch immer der wandert.

Darum nochmal die Frage: passiert das ueberhaupt bei dir, wenn du Qt5 verwendest?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Habe das mal so geändert:

Code: Alles auswählen

    def _item_clicked(self, index):
        # numeric position of dataset
        pos2dataset = index.row()
        print('pos2dataset', pos2dataset)
        # in front of item
        item_after = index.sibling(1,0)
        print('item_after', item_after)
        # in front of item
        item_before = index.sibling(-1,0)
        print('item_before', item_before)
Ich verwende ja PyQt5 mit dem Test-Code in einem seperaten Modul.
Dass etwas nicht funktioniert, das ist nicht der Fall.
Klicke ich auf ein x-beliebiges Item, bekomme ich obige Printanweisungen ausgegeben.
Also das sollte so funktionieren, wie bei Dir auch.

Wie bekomme ich z.B. beim Beenden einen FocusOut aus dem aktuellen Item?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast meine Frage immer noch nicht beantwortet. Sinn und Zweck deiner ganzen Fokussiererei soll doch die Übernahme der letzten Änderung sein. Weil du behauptest
Hintergrund ist der, dass bei Änderung bei einem beliebigen Item, wo ich nicht aus dem Item herausgehe, die Änderung nicht übernommen wird. Ganz klar man muss aus dem betreffenden Item in ein anderes gehen, damit die Änderung greift.
DAS IST FÜR MICH NICHT DER FALL!

Wenn ich das MDIFensterchen schließe, OHNE vorher den Focus verändert zu haben, dann werden die Änderungen übernommen. Punkt.

Wenn du das mit meinem Code und PyQt5 machst - was passiert dann? Sind die Änderungen übernommen?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo __deets__,

leider habe ich mich zu sehr auf FocusIn und FocusOut konzentriert und dabei das Endresultat vernachlässigt.
Du hast Recht, beim Schließen des Dataset-Fensters, wird die Änderung übernommen, ohne dass es zuvor den Focus verändert.
Da bin ich echt überrascht!
Ich habe dann, die Funktion self._item_clicked entfernt und es funktioniert noch immer.
Ist das der Unterschied zwischen Qt4 und Qt5?

Was mich noch interessiert, wenn ich mit der TAB-Taste mich bewege, wie würde das dann ala self._item_clicked aussehen?

Verzeih mir, dass ich dazu so lange gebraucht habe!

Grüße Nobuddy
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die _item_clicked Methode war nur Spielerei. Die hat keine Auswirkung gehabt, weil ich ja das Problem gar nicht hatte. Und wie ich schon wiederholt schrieb: ja, ich denke das ist Qt4 vs Qt5.

BRAUCHST du diese Tab-Geschichte? Oder ist das nur mildes Interesse? Ich muss sowas genauso wie du nachlesen. Mir mag das dank Erfahrung & Englisch leichter fallen, aber Zeit kostet es trotzdem, und wenn es nicht benötigt wird, spendiere ich die lieber woanders.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Die Tab-Geschichte, brauche ich vorerst nicht, später ja.
Aber da kann ich mich ja nochmals melden.

Bin gerade dabei meinen bisherigen Qt4-Code in Qt5-Code umzuwandeln.
Puh, da hat sich wirklich was getan und verändert...

Grüße und Danke für Deine Hilfe!
Nobuddy
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Komme nochmals auf das Thema zurück.
Der bisherige Code, mit focusEvent funktioniert, geändertes Item wird ohne Fkus setzen auf ein anderes Item übernommen.
Da bei dem bisherigen Test-Code, kein Close-Button integriert war, konnten wir auch dies nicht testen.
Die geänderten Daten des Items ohne FocusOut, werden nicht übernommen.
Deshalb habe ich mich bemüht, dies zu ändern.
Dank Deiner bisherigen nicht benötigten Funktion "_item_clicked", habe ich eine Lösung gefunden.
Ich poste mal den Teil-Code dazu, siehe bei Funktion "close" die erste Zeile.

Code: Alles auswählen


    def _item_clicked(self, index):
        # numeric position of dataset
        pos2dataset = index.row()
        # in front of item
        self.item_after = index.sibling(1,0)
        # in front of item
        item_before = index.sibling(-1,0)

    def exec_(self):
        self.view.setCurrentIndex(self.item_after)
        self.dataset = [self.model.invisibleRootItem().child(i).text()
            for i in range(len(self.header))]
        if self.dataset != self.old_dataset:
            r = MessageBox(QMessageBox.Yes, 'Änderung speichern?')
            if r.result() != QMessageBox.Yes:
                self.dataset = self.old_dataset
        print('self.dataset', self.dataset)
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich kann nur raten was du wirklich meinst: das man das Hauptfenster schliessen kann, ohne das es zu einer Uebernahme der Daten kommt? Das loest man mE anders. Du solltest das auf globaler Ebene verankern. Dein gesamtes Programm sollte nicht beendet werden koennen, wenn irgendwo Daten eingegeben wurden. Mir ist so, als ob es dazu einen Mechanismus gibt, muss den mal recherchieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das ist ja auch genau das Verhalten was Benutzer von anderen Programmen her kennen, dass ein Programm nachfragt ob das Schliessen abgebrochen werden soll, oder die veränderten Daten vor dem Schliessen gespeichert oder verworfen werden sollen. Wenn man die Daten einfach ungefragt speichert, kann das Benutzer auch sehr verärgern, weil man damit nicht rechnet das so etwas einfach ungefragt passiert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich habe alle Buttons zentral in der MDI angelegt, die steuen dann das activeSubWindow.
Die benötigten Buttons für das activeSubWindow werden entsprechend aktualisiert.
Die Buttons, sind in einem ToolBar verankert, ich sehe das als einfachste Lösung.

Zuerst hatte ich die Buttons in den jeweiligen activeSubWindow integriert, bin dann aber zu der Auffassung gekommen, dies zu zentralisieren. Einen Datensatz zu editieren, löschen oder einen Neuen hinzufügen, bleibt letztednlich immer das Gleiche.

Nun nochmals auf meinen letzten Post zurück zu kommen.
Beim Beenden des Dataset-Windows durch den Close-Button, werden Änderungen nicht übernommen, wenn man nicht das Item verlässt. Ganz anderst ist es ja, wenn ich das Dataset-Window durch den closeEvent beende. Das hat mich auch zu letzterer Lösung gebracht. Das sichert die gemachte Änderung, die dann durch eine MessageBox abgefragt wird (Bestätigung positiv/negativ).
Wenn es dafür noch eine einfachere Lösung gibt, kannst Du mir das gerne aufzeigen, ich lerne immer gerne dazu.
Antworten