FocusIn, Item-Position in Dataset-Tabelle ermitteln

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

Freitag 1. Februar 2019, 14:00

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 14:31

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 14:41

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 14:54

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 15:09

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 15:24

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 15:47

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 17:55

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 18:13

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 18:40

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 19:10

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Freitag 1. Februar 2019, 19:56

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 1. Februar 2019, 20:07

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: 833
Registriert: Montag 30. Januar 2012, 16:38

Samstag 2. Februar 2019, 09:51

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: 6215
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 2. Februar 2019, 10:39

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.
Antworten