Fokus auf bestimmtes Item setzen

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
Für die Bearbeitung von Datensätzen aus einer Liste, habe ich unten folgenden Code erstellt.
Dieser ist lauffähig, um ihn testen zu können.
Mein Problem ist, dass bei bestimmten Ereignissen, der Focus auf das betreffende Item gesetzt werden soll. Siehe dazu die Funktion "self.VerificationCompulsoryEntryFields".
Leider konnte ich noch keine Lösung finden, die auch bei mir funktioniert hätte.
Daher würde ich mich sehr über Eure Hilfe freuen!

Code: Alles auswählen

import sys
import datetime
date_now = datetime.date.today()
date_time = datetime.datetime.now().strftime('%Y.%m.%d_%H:%M:%S')
from PyQt5.QtCore import Qt, QEvent, QSize, QSortFilterProxyModel
from PyQt5.QtGui import QColor, QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (QAction, QApplication, QGridLayout, 
    QHeaderView, QLineEdit, QMainWindow, QMdiArea, 
	QMessageBox, QPushButton, QTableView)

title = 'Backlight management'
listname = 'addresses'
header = ['client', 'client_number', 'supplier_number',
    'namen', 'zusatz', 'anrede2', 'namen2', 'info_namen',
    'street', 'plz', 'ort', 'entfernung', 'homepage',
    'ust_nr', 'steuer_nr', 'handelsregister', 'handelsregister_nr', 'datum']
dataset = ['CUSTOMER', '029002', '', '','', '', '','', 'DIRE STREET 15', 
    '04711', 'CRAZY CITY', '0', '', '', '', '', '', '2019.03.22']
write_lock = {0 : True, 1 : True}
duty_fields = {0 : True, 1 : True, 3 : True, 8 : True, 9 : True, 10 : True}


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi = QMdiArea()
        self.setCentralWidget(self.mdi)
        self.mdi.tileSubWindows()
        self.listname = listname
        sub = Dataset(self, dataset, header)
        self.mdi.addSubWindow(sub)
        sub.show()

class Dataset(QLineEdit):

    def __init__(self, parent, dataset, header):
        super(Dataset, self).__init__()

        self.parent = parent
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.mdi = parent.mdi
        self.listname = parent.listname
        self.write_lock = write_lock
        self.duty_fields = duty_fields
        self.error_sign = '<?>'
        self.installEventFilter(self)
        self.setFocusPolicy(Qt.StrongFocus)
        self.view = QTableView(self)
        self.width = self.columnWidth()
        if self.width < 500:
            self.width = 500
        self.bt1 = QPushButton('Save_close', self)
        self.bt1.clicked.connect(self.save_close)
        # GridLayout
        grid = QGridLayout()
        grid.addWidget(self.view, 0, 0)
        grid.addWidget(self.bt1, 1, 0)
        self.setLayout(grid)
        self.model = QStandardItemModel()
        # Load and check dataset
        self.VerificationCompulsoryEntryFields (self.createDataset())
        # 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)
        self.view.horizontalHeader().setSectionResizeMode(
		    0, QHeaderView.Stretch)

    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()
        if width < self.width:
            width = self.width
        return QSize(width, height)
    
    def columnWidth(self):
        md = dict([(len(value), value) for value in self.dataset])
        mv = md[max(md)]
        width = round(
            QLineEdit().fontMetrics().boundingRect(mv).width() * 1.1)
        if width < 500:
            width = 500
        return width

    def eventFilter(self, source, event):
        """
        View events and check for taskstype.
        """
        keyWork = False
        try:
            if event.type() == QEvent.KeyRelease:
                c = 1
                if event.key() == Qt.Key_Tab:
                    keyWork = 'key_tab'
                elif event.key() == Qt.Key_Up:
                    keyWork = 'key_up'
                    c = -1
                elif event.key() == Qt.Key_Down:
                    keyWork = 'key_down'
                if keyWork:
                    i = self.view.currentIndex().row() - c
                    self.checkItem(i)
                    #self.writeLockCheck(keyWork)
        except AttributeError:
            pass
        return super().eventFilter(source, event)

    def valueFormat(self, i):
        self.column_name = self.header[i].lower()
        #return SetValue(self)
    
    def checkItem(self, i):
        item = self.model.invisibleRootItem().child(i)
        self.value = item.text()
        #self.valueFormat(i)
        item.setText(self.value)
        item.setData(i, Qt.UserRole)
        try:
            self.duty_fields[i]
            if item.text() == '' or item.text() == self.error_sign:
                # item compulsory entry is not true text color is red
                if item.text() == '':
                    item.setText(self.error_sign)
                item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            pass
        try:
            self.write_lock[i]
            # item read-only, set flags
            item.setFlags( Qt.ItemIsSelectable |  Qt.ItemIsEnabled )
            # item read-only, text color is red
            item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            if item.text() != self.error_sign:
                # item compulsory entry is true, text color is white
                item.setData(QColor(255,255,255), Qt.ForegroundRole)
        item.data(Qt.UserRole)
        self.model.setItem(i, 0, item)
        return item
    
    def createDataset(self):
        [self.model.invisibleRootItem().appendRow(QStandardItem(value))
            for i, value in enumerate(self.dataset)]
        return self.checkDataset()
    
    def checkDataset(self):
        return [self.checkItem(i).text() for i in range(len(self.header))]

    def VerificationCompulsoryEntryFields (self, dataset):
        self.mdi.duty_error = False
        if set((self.error_sign,)) & set(dataset):
            self.mdi.duty_error = True
            for i, value in enumerate(dataset):
                if self.checkItem(i).text() == self.error_sign:
                    #item.setFocus()	# set focus to item, no function
                    #item.selectAll()	# no function
                    break
            work_info = 'Eingabefeld muss ausgefüllt werden!'
            self.mdi.work_info = work_info
            print(work_info)
            return
        self.dataset = dataset
        return True

    def updateDataset(self):
        # update model table
        self.parent.model.data = sorted(self.parent.model.data)
        self.parent.view.setModel(self.mdi.model)
        self.parent.model.layoutChanged.emit()

    def save_close(self):
        if self.VerificationCompulsoryEntryFields (self.checkDataset()):
            self.parent.model.data[self.parent.index] = self.dataset
            self.updateDataset()


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

Bin einen Schritt weiter, hier der aktuelle Code:

Code: Alles auswählen

import sys
import datetime
date_now = datetime.date.today()
date_time = datetime.datetime.now().strftime('%Y.%m.%d_%H:%M:%S')
from PyQt5.QtCore import (Qt, QEvent, QItemSelection, QSize, 
    QItemSelectionModel, pyqtSlot)
from PyQt5.QtGui import QColor, QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (QAbstractItemView, QAction, QApplication, 
    QGridLayout, QHeaderView, QLineEdit, QMainWindow, QMdiArea, 
	QMessageBox, QPushButton, QTableView, QTabWidget)

title = 'Backlight management'
listname = 'addresses'
header = ['client', 'client_number', 'supplier_number',
    'namen', 'zusatz', 'anrede2', 'namen2', 'info_namen',
    'street', 'plz', 'ort', 'entfernung', 'homepage',
    'ust_nr', 'steuer_nr', 'handelsregister', 'handelsregister_nr', 'datum']
dataset = ['CUSTOMER', '029002', '', '','', '', '','', 'DIRE STREET 15', 
    '04711', 'CRAZY CITY', '0', '', '', '', '', '', '2019.03.22']
write_lock = {0 : True, 1 : True}
duty_fields = {0 : True, 1 : True, 3 : True, 8 : True, 9 : True, 10 : True}


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi = QMdiArea()
        self.setCentralWidget(self.mdi)
        self.mdi.tileSubWindows()
        self.listname = listname
        sub = Dataset(self, dataset, header)
        self.mdi.addSubWindow(sub)
        sub.show()

class Dataset(QLineEdit):

    def __init__(self, parent, dataset, header):
        super(Dataset, self).__init__()

        self.parent = parent
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.mdi = parent.mdi
        self.listname = parent.listname
        self.write_lock = write_lock
        self.duty_fields = duty_fields
        self.error_sign = '<?>'
        self.installEventFilter(self)
        self.setFocusPolicy(Qt.StrongFocus)
        self.view = QTableView(self)
        self.width = self.columnWidth()
        if self.width < 500:
            self.width = 500
        self.bt1 = QPushButton('Save_close', self)
        self.bt1.clicked.connect(self.save_close)
        # GridLayout
        grid = QGridLayout()
        grid.addWidget(self.view, 0, 0)
        grid.addWidget(self.bt1, 1, 0)
        self.setLayout(grid)
        self.model = QStandardItemModel()
        # Load and check dataset
        self.VerificationCompulsoryEntryFields(self.createDataset())
        # Vertical header
        [self.model.setHeaderData(i, Qt.Vertical, column)
            for i, column in enumerate(header)]
        self.view.setModel(self.model)
        self.view.horizontalHeader().setSectionResizeMode(
		    0, QHeaderView.Stretch)
        # selection model
        self.selection_model = self.view.selectionModel()
        self.selection_model.selectionChanged.connect(self.on_selectionChanged)
        # set focus on first writable item
        self.setFocus()

    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()
        if width < self.width:
            width = self.width
        return QSize(width, height)
    
    def columnWidth(self):
        md = dict([(len(value), value) for value in self.dataset])
        mv = md[max(md)]
        width = round(
            QLineEdit().fontMetrics().boundingRect(mv).width() * 1.1)
        if width < 500:
            width = 500
        return width
    
    @pyqtSlot('QItemSelection', 'QItemSelection')
    def on_selectionChanged(self, selected, deselected):
        for ix in selected.indexes():
            print("selected: ", ix.data())
            self.selectedItem = ix.row()
        for ix in deselected.indexes():
            print("deselected: ", ix.data())
            self.deselectedItem = ix.row()

    def eventFilter(self, source, event):
        """
        View events and check for taskstype.
        """
        keyWork = False
        try:
            if event.type() == QEvent.KeyRelease:
                c = 1
                if event.key() == Qt.Key_Tab:
                    keyWork = 'key_tab'
                elif event.key() == Qt.Key_Up:
                    keyWork = 'key_up'
                    c = -1
                elif event.key() == Qt.Key_Down:
                    keyWork = 'key_down'
                if keyWork:
                    i = self.view.currentIndex().row() - c
                    self.checkItem(i)
                    #self.writeLockCheck(keyWork)
        except AttributeError:
            pass
        return super().eventFilter(source, event)
        
    def setFocus(self):
        index = self.model.index(self.focusItem, 0)
        self.selection_model.select(index, 
            QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.selection_model.setCurrentIndex(index,
                QItemSelectionModel.ClearAndSelect)


    def valueFormat(self, i):
        self.column_name = self.header[i].lower()
        #return SetValue(self)
    
    def checkItem(self, i):
        item = self.model.invisibleRootItem().child(i)
        self.value = item.text()
        #self.valueFormat(i)
        item.setText(self.value)
        item.setData(i, Qt.UserRole)
        try:
            self.duty_fields[i]
            if item.text() == '' or item.text() == self.error_sign:
                # item compulsory entry is not true text color is red
                if item.text() == '':
                    item.setText(self.error_sign)
                item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            try:
                self.focusItem
            except AttributeError:
                self.focusItem = i
        try:
            self.write_lock[i]
            # item read-only, set flags
            item.setFlags( Qt.ItemIsSelectable |  Qt.ItemIsEnabled )
            # item read-only, text color is red
            item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            if item.text() != self.error_sign:
                # item compulsory entry is true, text color is white
                item.setData(QColor(255,255,255), Qt.ForegroundRole)
        item.data(Qt.UserRole)
        self.model.setItem(i, 0, item)
        return item
    
    def createDataset(self):
        [self.model.invisibleRootItem().appendRow(QStandardItem(value))
            for i, value in enumerate(self.dataset)]
        return self.checkDataset()
    
    def checkDataset(self):
        return [self.checkItem(i).text() for i in range(len(self.header))]

    def VerificationCompulsoryEntryFields (self, dataset):
        self.mdi.duty_error = False
        if set((self.error_sign,)) & set(dataset):
            self.mdi.duty_error = True
            for i, value in enumerate(dataset):
                if self.checkItem(i).text() == self.error_sign:
                    self.focusItem = i
                    try:
                        self.setFocus()
                    except AttributeError:
                        # start __init__
                        pass
                    break
            work_info = 'Eingabefeld muss ausgefüllt werden!'
            self.mdi.work_info = work_info
            print(work_info)
            return
        self.dataset = dataset
        return True

    def updateDataset(self):
        # update model table
        self.parent.model.data = sorted(self.parent.model.data)
        self.parent.view.setModel(self.mdi.model)
        self.parent.model.layoutChanged.emit()

    def save_close(self):
        if self.VerificationCompulsoryEntryFields (self.checkDataset()):
            self.parent.model.data[self.parent.index] = self.dataset
            self.updateDataset()


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

if __name__ == '__main__':
    main()
Focus, wird auf das gewünschte Item gesetzt.
Leider ist eine Eingabe, erst nach dem Betätigen der TAB-Taste möglich!
Gibt es dafür eine Lösung?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Auch wenn ich bis jetzt noch kein Feedback erhalten habe, poste ich meinen aktuellen Code zu meinem Projekt.
Ich habe soweit mir es aufgefallen ist, Überflüssiges aus dem Code entfernt.
Schreibgeschützte Felder, werden nicht mehr angesteuert.
Den Focus, erhalten nur Felder, die nicht schreibgeschützt sind.

Was ich noch nicht lösen konnte, ist dass:
- beim Start, das Feld das den Focus hat, nicht direkt bearbeitet / editiert werden kann
- das Gleiche, bei Funktion "self.save_close"
Erst durch Betätigen der TAB-Taste, wird das Feld mit dem Focus aktiviert und kann dann bearbeitet / editiert werden, was nicht gerade funktionell ist.
Daher möchte ich nochmals nachfragen, ob es da wirklich keine Lösung gibt, bzw. was an meinem Code falsch ist?
Würde mich wirklich über eine Antwort von Euch freuen, egal wie sie ausfällt!!!!!!

Hier nun mein aktueller Code:

Code: Alles auswählen

import sys
import datetime
date_now = datetime.date.today()
date_time = datetime.datetime.now().strftime('%Y.%m.%d_%H:%M:%S')
from PyQt5.QtCore import (Qt, QEvent, QItemSelection, QSize, 
    QItemSelectionModel, pyqtSlot)
from PyQt5.QtGui import QColor, QCursor, QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (QAbstractItemView, QAction, QApplication, 
    QGridLayout, QHeaderView, QLineEdit, QMainWindow, QMdiArea, 
	QMessageBox, QPushButton, QTableView)

title = 'Backlight management'
listname = 'addresses'
header = ['client', 'client_number', 'supplier_number',
    'namen', 'zusatz', 'anrede2', 'namen2', 'info_namen',
    'street', 'plz', 'ort', 'entfernung', 'homepage',
    'ust_nr', 'steuer_nr', 'handelsregister', 'handelsregister_nr', 'datum']
dataset = ['CUSTOMER', '029002', '', '','', '', '','', 'DIRE STREET 15', 
    '04711', 'CRAZY CITY', '0', '', '', '', '', '', '2019.03.22']
write_lock = {0 : True, 1 : True}
duty_fields = {0 : True, 1 : True, 3 : True, 8 : True, 9 : True, 10 : True}


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("MDI demo")
        self.mdi = QMdiArea()
        self.setCentralWidget(self.mdi)
        self.mdi.tileSubWindows()
        self.listname = listname
        sub = Dataset(self, dataset, header)
        self.mdi.addSubWindow(sub)
        sub.show()

class Dataset(QLineEdit):

    def __init__(self, parent, dataset, header):
        super(Dataset, self).__init__()

        self.setObjectName('DATASET')
        self.parent = parent
        self.mdi = parent.mdi
        self.old_dataset = self.dataset = dataset
        self.header = header
        self.listname = parent.listname
        self.write_lock = write_lock
        self.duty_fields = duty_fields
        self.error_sign = '<?>'
        self.setFocusPolicy(Qt.StrongFocus)
        self.view = QTableView(self)
        self.width = self.columnWidth()
        if self.width < 500:
            self.width = 500
        self.bt1 = QPushButton('Save_close', self)
        self.bt1.clicked.connect(self.save_close)
        # GridLayout
        grid = QGridLayout()
        grid.addWidget(self.view, 0, 0)
        grid.addWidget(self.bt1, 1, 0)
        self.setLayout(grid)
        self.model = QStandardItemModel()
        # Load dataset
        self.createDataset()
        # Vertical header
        [self.model.setHeaderData(i, Qt.Vertical, column)
            for i, column in enumerate(header)]
        self.view.setModel(self.model)
        self.view.horizontalHeader().setSectionResizeMode(
		    0, QHeaderView.Stretch)
        # Cursor
        self.view.setCursor(Qt.PointingHandCursor)
        # selection model
        self.selection_model = self.view.selectionModel()
        self.selection_model.selectionChanged.connect(self.on_selectionChanged)
        # set focus on first writable item
        self.setFocus()

    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()
        if width < self.width:
            width = self.width
        return QSize(width, height)
    
    def columnWidth(self):
        md = dict([(len(value), value) for value in self.dataset])
        mv = md[max(md)]
        width = round(
            QLineEdit().fontMetrics().boundingRect(mv).width() * 1.1)
        if width < 500:
            width = 500
        return width
    
    @pyqtSlot('QItemSelection', 'QItemSelection')
    def on_selectionChanged(self, selected, deselected):
        for ix in selected.indexes():
            print("selected: ", ix.row())
            self.selectedItem = ix.row()
        for ix in deselected.indexes():
            print("deselected: ", ix.row())
            self.deselectedItem = ix.row()
        self.checkItem(self.selectedItem)
        self.jumpOverWriteLock()
        
    def jumpOverWriteLock(self):
        """
        Set the FOCUS to the next input field without write protection. 
        """
        i = self.selectedItem
        while True:
            self.focusItem = i
            self.setFocus()
            if not self.write_lock.get(i):
                break
            if i < (len(self.header) - 1):
                i += 1
            elif i == (len(self.header) - 1):
                i = 0
        return
        
    def setFocus(self):
        index = self.model.index(self.focusItem, 0)
        self.selection_model.select(index, 
            QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.selection_model.setCurrentIndex(index,
            QItemSelectionModel.ClearAndSelect)

    def valueFormat(self, i):
        self.column_name = self.header[i].lower()
        #return SetValue(self)
    
    def checkItem(self, i):
        item = self.model.invisibleRootItem().child(i)
        self.value = item.text()
        #self.valueFormat(i)
        item.setText(self.value)
        item.setData(i, Qt.UserRole)
        try:
            self.duty_fields[i]
            if item.text() == '' or item.text() == self.error_sign:
                # item compulsory entry is not true text color is red
                if item.text() == '':
                    item.setText(self.error_sign)
                item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            pass
        try:
            self.write_lock[i]
            # item read-only, set flags
            item.setFlags( Qt.ItemIsSelectable |  Qt.ItemIsEnabled )
            # item read-only, text color is red
            item.setData(QColor(255,0,0), Qt.ForegroundRole)
        except KeyError:
            try:
                self.focusItem
            except AttributeError:
                self.focusItem = i
            if item.text() != self.error_sign:
                # item compulsory entry is true, text color is white
                item.setData(QColor(255,255,255), Qt.ForegroundRole)
        item.data(Qt.UserRole)
        self.model.setItem(i, 0, item)
        return item
    
    def createDataset(self):
        [self.model.invisibleRootItem().appendRow(QStandardItem(value))
            for i, value in enumerate(self.dataset)]
        return self.checkDataset()
    
    def checkDataset(self):
        return [self.checkItem(i).text() for i in range(len(self.header))]

    def VerificationCompulsoryEntryFields(self, dataset):
        self.mdi.duty_error = False
        if set((self.error_sign,)) & set(dataset):
            self.mdi.duty_error = True
            for i, value in enumerate(dataset):
                if self.checkItem(i).text() == self.error_sign:
                    self.focusItem = i
                    self.setFocus()
                    break
            work_info = 'Eingabefeld muss ausgefüllt werden!'
            self.mdi.work_info = work_info
            print(work_info)
            return
        self.dataset = dataset
        return True

    def updateDataset(self):
        # update model table
        self.parent.model.data = sorted(self.parent.model.data)
        self.parent.view.setModel(self.mdi.model)
        self.parent.model.layoutChanged.emit()

    def save_close(self):
        if self.VerificationCompulsoryEntryFields(self.checkDataset()):
            self.parent.model.data[self.parent.index] = self.dataset
            self.updateDataset()


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

Das von mir beschriebene Problem, tritt nur in meinem geposteten Code auf.
Bei Integrierung in mein Projekt, funktioniert der Code ohne dieses Problem! :roll: :) :wink:
Antworten