QTableView - Auswahlmenü mit Completer

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

Hallo zusammen,

erstelle im MDI mit QTableView meine Tabellen.
Mit der Return-Taste kann ich selektierte Tabellenzeilen als Dataset ausgeben lassen.
Diese Daten können dann editiert werden.

Nun möchte ich in dem Dataset, an einer bestimmten Position ein Auswahlmenü verknüpfen, um so den Text des betreffende Item zu ändern.

Leider komme ich alleine nicht weiter und würde mich so auf Eure Hilfe freuen!

Ich habe dazu einen lauffähigen Code anzubieten, damit Ihr Euch vorstellen könnt, wie das mit der Dataset-Ausgabe funktioniert.

Wichtige Zeilenpositionen sind:
162 - 173 = TableItemCompleter
204 - 206 = Versuch Auswahlmenü zu integrieren

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# For Python3.x

import sys
import operator  # used for sorting
import random
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, QSize, QEvent, QSortFilterProxyModel
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (QMainWindow, QMdiArea, QWidget, QTableView,
    QAbstractItemView, QGridLayout,
    QStyledItemDelegate, QCompleter, QLineEdit, QApplication)


title = 'Backlight management'
header = ['Pos', 'Supplier', 'Artikel', 'Benennung']
table = [['001', 'Meyer', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch'],
    ['002', 'Mueller', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch'],
    ['003', 'Mauser', '47110', 'Bratwurst weiss, 125 g / Stück, 50% Schweinefleisch, 50% Rindfleisch']]
columnWidths = [20, 30, 40, 400]

completer_list = ['Mauser', 'Meyer', 'Mueller']


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi_area = QMdiArea()
        self.setCentralWidget(self.mdi_area)
        self.mdi_area.tileSubWindows()
        title = 'TEST'
        self.list_model = 'listinlist'
        sub = Table(self, title, table, header, columnWidths)
        self.mdi_area.addSubWindow(sub)
        sub.show()


class Table(QWidget):
    def __init__(self, parent, title, dataList, header, columnWidths):
        QWidget.__init__(self)
        self.parent = parent
        self.title = title
        self.old_data = self.mylist = self.dataList = sorted(dataList)
        self.header = header
        self.columnWidths = columnWidths
        self.mdi_area = parent.mdi_area
        self.setWindowTitle(self.title) 
        self.installEventFilter(self)
        self.view = QTableView()
        self.model = TableModel(self, self.dataList, self.header)
        self.update_table()
        self.headerSize(parent)
        # GridLayout
        grid = QGridLayout() 
        grid.addWidget(self.view, 0, 0)
        self.setLayout(grid) 

    def updateGeometryAsync(self):
        QtCore.QTimer.singleShot(0, self.updateGeometry)

    def headerSize(self, parent):
        """
        Build header- and columnsize
        """
        view = self.view
        model = self.model
        view.setModel(model)
        view.resizeColumnsToContents()
        view.resizeRowsToContents()
        view.setWordWrap(True)

    def update_table(self):
        # ... when a row header label changes and makes the
        # width of the vertical header change too       
        self.model.headerDataChanged.connect(self.updateGeometryAsync)
        self.view.setModel(self.model)
        # enable sorting
        self.view.setSortingEnabled(True)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.old_data_counter = len(self.mylist)
        self.mdi_area.adjustSize()
        index = self.view.model().index(0, 1)
        self.view.setCurrentIndex(index)

    def eventFilter(self, widget, event):
        if event.type() == QtCore.QEvent.KeyPress and widget is self:
            key = event.key()
            if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
                DatasetView(self, self.view, self.model)
            return True
        return QWidget.eventFilter(self, widget, event)


class TableModel(QtCore.QAbstractTableModel):
    """
    Build table
    """

    def __init__(self, parent, mylist, header, *args):
        QtCore.QAbstractTableModel.__init__(self, parent, *args)
        self.mylist = mylist
        self.header = header

    def rowCount(self, parent):
        return len(self.mylist)

    def columnCount(self, parent):
        return len(self.mylist[0])

    def data(self, index, role):
        if not index.isValid() or role != QtCore.Qt.DisplayRole:
            return None
        return self.mylist[index.row()][index.column()]

    def headerData(self, col, orientation, role):
        if (orientation == QtCore.Qt.Horizontal
                and role == QtCore.Qt.DisplayRole):
            return self.header[col]
        return None

    def sort(self, col, order):
        """
        Sort table by given column number col
        """

        self.layoutAboutToBeChanged.emit()
        self.mylist = sorted(self.mylist,
            key=operator.itemgetter(col))
        if order == QtCore.Qt.DescendingOrder:
            self.mylist #.reverse()
        self.layoutChanged.emit()

    def output_list(self):
        return self.mylist


class DatasetView(object):

    #def datasetView(self):
    def __init__(self, parent, view, model):

        self.parent = parent
        self.model = model
        self.mdi_area = self.parent.mdi_area
        i = QAbstractItemView.SelectRows
        self.mylist = model.output_list()
        self.index = view.selectedIndexes()[0].row()
        dataset = self.mylist[self.index]
        header = parent.header
        width = 300
        dialog = Dataset(self, dataset, header, width)
        self.new_child_window(dialog)

    def new_child_window(self, dialog):
        # Show dataset window
        sub = self.mdi_area.addSubWindow(dialog).show()


class TableItemCompleter(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        print('#######################')
        print('index', index)
        editor = QLineEdit(parent)
        print('editor', editor)
        print('index.data', index.data(), index.data(Qt.UserRole))
        completion_ls = index.data(Qt.UserRole) # get list
        completer = QCompleter(completion_ls, parent)
        editor.setCompleter(completer)
        print('#######################')
        return editor


class Dataset(QWidget): 

    def __init__(self, parent, dataset, header, columnWidths): 
        super(Dataset, self).__init__()
        self.parent = parent
        self.dataset = dataset
        self.header = header
        self.columnWidths = columnWidths
        self.setObjectName('DATASET')
        self.installEventFilter(self)
        self.setFocusPolicy(Qt.StrongFocus)
        self.view = QTableView(self)
        self.view.clicked.connect(self._item_clicked)
        # GridLayout
        grid = QGridLayout() 
        grid.addWidget(self.view, 0, 0)
        self.setLayout(grid)
        self.model = QStandardItemModel(self)
        # Load dataset
        [self.model.invisibleRootItem().appendRow(
            QStandardItem(column)) for column in self.dataset]
        # Vertical header
        [self.model.setHeaderData(i, Qt.Vertical, column)
            for i, column in enumerate(header)]
        self.proxy = QSortFilterProxyModel(self) 
        self.proxy.setSourceModel(self.model)
        self.view.setModel(self.proxy)
        # set completer for item
        self.view.setItemDelegate(TableItemCompleter(
            self.model.invisibleRootItem().child(1).setData(
            (Qt.UserRole, random.sample(completer_list, 1)))))

    def sizeHint(self):
        # and the margins which include the frameWidth and the extra
        # margins that would be set via a stylesheet or something else
        margins = self.contentsMargins()
        # y
        height = round(self.fontMetrics().height() * 1.1)
        height += self.view.verticalHeader().length()
        height += self.view.horizontalHeader().height()
        height += self.view.horizontalScrollBar().height()
        height += margins.left() + margins.right()
        # x
        width = self.view.horizontalHeader().length()
        width += self.view.verticalHeader().width()
        width += self.view.verticalScrollBar().width()
        width += margins.left() + margins.right()
        return QSize(width, height)

    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 main():
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())


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

Habe nach einigen Versuchen, nun die Lösung selbst gefunden. :P

Ich habe Zeile 204 - 206 wie folgt geändert:

Code: Alles auswählen

        item = self.model.invisibleRootItem().child(1)
        item.setData(completer_list, Qt.UserRole)
        item.data(Qt.UserRole)
        self.view.setItemDelegate(TableItemCompleter(self.model))
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Was mich jetzt noch beschäftigt, ob es eine Möglichkeit gibt, die Groß-/Kleinschreibung "auszutricksen"?
Egal ob ich ein Kleinbuchstabe oder ein Großbuchstabe in dem Auswahlfeld eingebe, die betreffende Textauswahl ausgegeben wird?

Ich poste dazu den betreffenden Code:

Code: Alles auswählen

class TableItemCompleter(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)
        completion_ls = index.data(Qt.UserRole) # get list
        completer = QCompleter(completion_ls, parent)
        editor.setCompleter(completer)
        return editor

Code: Alles auswählen

        # set completer for item
        item = self.model.invisibleRootItem().child(1)
        item.setData(completer_list, Qt.UserRole)
        item.data(Qt.UserRole)
        self.view.setItemDelegate(TableItemCompleter(self.model))
Hoffe, Ihr könnt mir dabei helfen!?

Grüße Nobuddy
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nobuddy: Schau Dir doch mal die Qt-Properties auf so einem `QCompleter` an.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

__BlackJack__, Danke für den Tip!

Zeile 6, müsste die Lösung sein:

Code: Alles auswählen

class TableItemCompleter(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)
        completion_ls = index.data(Qt.UserRole) # get list
        completer = QCompleter(completion_ls, parent)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        editor.setCompleter(completer)
        return editor
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Zwei weitere Fragen stellen sich mir.
Wie lässt sich das Popup-Fenster öffnen, wenn meine Eingabe keinen Treffer erzielt, damit ich eine Auswahl aus dem Popup-Fenster trotzdem treffen kann?

Bei dem Hinzufügen von:

Code: Alles auswählen

completer.setCompletionMode(completer.completionMode())
gibt es bei mir keine erkennbare Veränderung, wie ohne das Hinzufügen des Codes.
Was bewirkt das?
Wende ich diesen Code falsch an?

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

Das ist doch eine Tautologie, die du da programmmiert hast. Du setzt den gleichen Modus wie den, der schon vorhanden ist. Was erwartest du denn da fuer eine Aenderung? Du erwartest doch auch nicht das

Code: Alles auswählen

foo = foo
irgendetwas bewirkt.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo __deets__,

Deine Antwort gehört zur zweiten Frage.
Überflüssiger Code (doppelt gemoppelt), das habe ich verstanden.

Wie sieht es mit meiner ersten Frage aus?
Antworten