I want to create a QLineEdit field with basic code completion capability, but so far whenever I select an attribute of an item item.attr, the item. is replaced by attr rather than inserting attr after item.. Furthermore if that attr has attr.subattr, it is impossible to predict it because item. has been replaced and attr. does not exist at the root of my model.
I have created a relatively minimal example:
import sys
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication,QWidget,QVBoxLayout,QLineEdit,QCompleter
test_model_data = [
('tree',[ # tree
('branch', [ # tree.branch
('leaf',[])]), # tree.branch.leaf
('roots', [])]), # tree.roots
('house',[ # house
('kitchen',[]), # house.kitchen
('bedroom',[])]), # house.bedroom
('obj3',[]), # etc..
('obj4',[])
]
class codeCompleter(QCompleter):
def splitPath(self, path):
return path.split('.') #split table.member
class mainApp(QWidget):
def __init__(self):
super().__init__()
self.entry = QLineEdit(self)
self.model = QStandardItemModel(parent=self)
self.completer = codeCompleter(self.model, self)
self.entry.setCompleter(self.completer)
layout = QVBoxLayout()
layout.addWidget(self.entry)
self.setLayout(layout)
self.update_model() #normally called from a signal when new data is available
def update_model(self):
def addItems(parent, elements):
for text, children in elements:
item = QStandardItem(text)
parent.appendRow(item)
if children:
addItems(item, children)
addItems(self.model, test_model_data)
if __name__ == "__main__":
app = QApplication(sys.argv)
hwind = mainApp()
hwind.show()
sys.exit(app.exec_())
I came up with this approach from the Qt5 Docs and an example with Qt4.6, but neither combine all of what I'm trying to accomplish. Do I need a different model structure? Do I need to subclass more of QCompleter? Do I need a different Qt class?
gif of example: (sorry for quality)
Epilogue:
For those interested in actual code completion, I expanded on my code after integrating #eyllanesc's answer so that text before the matched sequence of identifiers was left alone (text ahead of the matched sequence does not prevent matching, nor is deleted when a new match is inserted). All it took was a little bit of regex to separate the part we want to complete from the preceeding text:
class CodeCompleter(QCompleter):
ConcatenationRole = Qt.UserRole + 1
def __init__(self, parent=None, data=[]):
super().__init__(parent)
self.create_model(data)
self.regex = re.compile('((?:[_a-zA-Z]+\w*)(?:\.[_a-zA-Z]+\w*)*\.?)$')
def splitPath(self, path): #breaks lineEdit.text() into list of strings to match to model
match = self.regex.search(path)
return match[0].split('.') if match else ['']
def pathFromIndex(self, ix): #gets model node (QStandardItem) and returns "text" for lineEdit.setText(text)
return self.regex.sub(ix.data(CodeCompleter.ConcatenationRole), self.completionPrefix())
The pathFromIndex() method returns the string that will be placed in the QLineEdit, instead it will return the concatenation of the text of the item and the texts of its predecessors. To make it more efficient and not calculate that online concatenation, a new role will be created to the model that contains that data.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit, QCompleter
test_model_data = [
('tree',[ # tree
('branch', [ # tree.branch
('leaf',[])]), # tree.branch.leaf
('roots', [])]), # tree.roots
('house',[ # house
('kitchen',[]), # house.kitchen
('bedroom',[])]), # house.bedroom
('obj3',[]), # etc..
('obj4',[])
]
class CodeCompleter(QCompleter):
ConcatenationRole = Qt.UserRole + 1
def __init__(self, data, parent=None):
super().__init__(parent)
self.create_model(data)
def splitPath(self, path):
return path.split('.')
def pathFromIndex(self, ix):
return ix.data(CodeCompleter.ConcatenationRole)
def create_model(self, data):
def addItems(parent, elements, t=""):
for text, children in elements:
item = QStandardItem(text)
data = t + "." + text if t else text
item.setData(data, CodeCompleter.ConcatenationRole)
parent.appendRow(item)
if children:
addItems(item, children, data)
model = QStandardItemModel(self)
addItems(model, data)
self.setModel(model)
class mainApp(QWidget):
def __init__(self):
super().__init__()
self.entry = QLineEdit(self)
self.completer = CodeCompleter(test_model_data, self)
self.entry.setCompleter(self.completer)
layout = QVBoxLayout()
layout.addWidget(self.entry)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
hwind = mainApp()
hwind.show()
sys.exit(app.exec_())
Related
I have a code that makes some QHBoxLayouts with some elements in them. I want to remove each Row class (QHBoxLayouts object) when clicking on their own remove button.
Here is the code for testing it
from sys import exit, argv
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QScrollArea, QPushButton, QLineEdit
class MainWindow(QWidget):
def __init__(self, parent = None) -> None:
super().__init__(parent)
self.sub_window = None
self.screenWidth = 1920
self.screenHeight = 1080
self.windowWidth = 1000
self.windowHeight = 800
self.setupUi()
self.show()
def setupUi(self) -> None:
self.setWindowTitle("Password Manager")
self.setGeometry((self.screenWidth - self.windowWidth) // 2, (self.screenHeight - self.windowHeight) // 2, self.windowWidth, self.windowHeight)
self.mainLayout = QVBoxLayout()
self.scrollAreaLayoutContents = QVBoxLayout()
self.scrollArea = QScrollArea()
self.scrollArea.setLayout(self.scrollAreaLayoutContents)
for i in range(10):
self.scrollAreaLayoutContents.addLayout(Row(f"{i}"))
self.mainLayout.addWidget(self.scrollArea)
self.setLayout(self.mainLayout)
class Row(QHBoxLayout):
def __init__(self, name:str) -> None:
super().__init__()
self.nameLineEdit = QLineEdit(f"{name}")
self.nameLineEdit.setDisabled(True)
self.addWidget(self.nameLineEdit)
self.removeButton = QPushButton("remove")
self.removeButton.clicked.connect(self.removeItSelf)
self.addWidget(self.removeButton)
def removeItSelf(self) -> None:
self.removeItem(self)
if __name__ == "__main__":
app = QApplication(argv)
window = MainWindow()
exit(app.exec())
I want to remove each Row class (QHBoxLayouts object) when clicking on their own remove button.
self.removeItem(self) will obviously not work, because removeItem() removes an item from the layout represented by self, and an item cannot remove itself from itself.
The removal should be done on the level of the parent layout, but there are three aspects that should be kept in mind:
there is no direct way to get the parent layout from a nested one;
removing the layout does not remove the widgets;
the layout could have other nested layout of its own;
The first point can be easily solved when talking about removal: since QLayout inherits from QObject, we can call deleteLater().
In your simple case, assuming that the contents are known and do not change, it could be done like this:
def removeItSelf(self) -> None:
self.nameLineEdit.deleteLater()
self.removeButton.deleteLater()
self.deleteLater()
In any other situations (for instance, if contents are dynamically added), the only way to ensure that all the contents get destroyed is by using a recursive function that iterates all layout items.
While that is feasible, there is a much simpler solution: instead of using a layout subclass, use a basic QWidget as a container. In that case, all widgets are children of the container, and deleting the container will automatically take care of their removal:
class Row(QWidget):
def __init__(self, name:str) -> None:
super().__init__()
layout = QHBoxLayout(self)
self.nameLineEdit = QLineEdit(f"{name}")
self.nameLineEdit.setDisabled(True)
layout.addWidget(self.nameLineEdit)
self.removeButton = QPushButton("remove")
self.removeButton.clicked.connect(self.removeItSelf)
layout.addWidget(self.removeButton)
def removeItSelf(self) -> None:
self.deleteLater()
class MainWindow(QWidget):
# ...
def setupUi(self) -> None:
# ...
for i in range(10):
self.scrollAreaLayoutContents.addWidget(Row(f"{i}"))
In my app I have a QTabWidget which holds a variable number of seemingly "identical" tabs with a variable number of widgets.
I want, once the TAB (or shift-TAB) button is pressed, for the focus of the app to move to the next (or previous) tab, and focus on the corresponding widget of that tab (the one corresponding to the widget which had the focus until the key press).
What is the best way to go around this in a simple way? I tried using a QShortcut to catch the key-press but I can't seem to figure out a way to get the corresponding widget in the next or previous tab and focus on that.
Here's a minimal example of the code, which simply moves to the next tab but not to the corresponding widget:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import *
class tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self)
shortcut.activated.connect(self.on_tab)
shortcut2 = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Backtab), self)
shortcut2.activated.connect(self.on_shift_tab)
self.tabs = []
for i in range(num_tabs):
newtab = QWidget()
self.tabs.append(newtab)
self.addTab(newtab, f'Tab {i}')
self.add_widgets_to(newtab)
def add_widgets_to(self, tab):
layout = QVBoxLayout()
tab.setLayout(layout)
layout.addWidget(QSpinBox())
layout.addWidget(QCheckBox())
gender = QHBoxLayout()
gender.addWidget(QRadioButton("Male"))
gender.addWidget(QRadioButton("Female"))
layout.addLayout(gender)
#QtCore.pyqtSlot()
def on_tab(self):
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab + 1) % self.count())
# TODO find current widget in focus, and find the corresponding one in the next tab, and focus on that one... note that widgets could be complex (i.e., not direct children...)
#QtCore.pyqtSlot()
def on_shift_tab(self):
print("do_something")
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab - 1) % self.count())
def main():
app = QApplication(sys.argv)
ex = tabdemo()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Since the OP indicates that each page will have identical components then an index can be associated so that the index of the tab can be obtained before changing it and then set the widget's focus then set the focus to the other corresponding widget.
import sys
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QHBoxLayout,
QRadioButton,
QShortcut,
QSpinBox,
QTabWidget,
QVBoxLayout,
QWidget,
)
class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
spinbox = QSpinBox()
checkbox = QCheckBox()
male_radio = QRadioButton("Male")
female_radio = QRadioButton("Female")
layout = QVBoxLayout(self)
layout.addWidget(spinbox)
layout.addWidget(checkbox)
gender = QHBoxLayout()
gender.addWidget(male_radio)
gender.addWidget(female_radio)
layout.addLayout(gender)
for i, widget in enumerate((spinbox, checkbox, male_radio, female_radio)):
widget.setProperty("tab_index", i)
class Tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)
for i in range(num_tabs):
page = Page()
self.addTab(page, f"Tab {i}")
#pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
#pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, index):
focus_widget = QApplication.focusWidget()
tab_index = focus_widget.property("tab_index") if focus_widget else None
self.setCurrentIndex(index)
if tab_index is not None and self.currentWidget() is not None:
for widget in self.currentWidget().findChildren(QWidget):
i = widget.property("tab_index")
if i == tab_index:
widget.setFocus(True)
def main():
app = QApplication(sys.argv)
ex = Tabdemo()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Building on eyllanesc's answer, I improved the functionality to:
Account for the scrollbar location (if exists)
Use a bi-directional dictionary (implemented here) instead of a linear lookup
Dynamically add all relevant widgets using the update_map() method instead of having to add each widget manually.
Posting in case anyone finds this useful.
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QWidget, QTabWidget, QShortcut, QApplication, QScrollArea
class BidirectionalDict(dict):
def __init__(self, *args, **kwargs):
super(BidirectionalDict, self).__init__(*args, **kwargs)
self.inverse = {}
for key, value in self.items():
self.inverse.setdefault(value, []).append(key)
def __setitem__(self, key, value):
if key in self:
self.inverse[self[key]].remove(key)
super(BidirectionalDict, self).__setitem__(key, value)
self.inverse.setdefault(value, []).append(key)
def __delitem__(self, key):
self.inverse.setdefault(self[key], []).remove(key)
if self[key] in self.inverse and not self.inverse[self[key]]:
del self.inverse[self[key]]
super(BidirectionalDict, self).__delitem__(key)
def get_first_inv(self, key):
return self.inverse.get(key, [None])[0]
class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.widgets_map = BidirectionalDict()
# ... add your widgets ...
self.update_map()
def update_map(self):
widgets = self.findChildren(QWidget)
for i, widget in enumerate(widgets):
self.widgets_map[i] = widget
class MyQTabWidget(QTabWidget):
def __init__(self):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)
#pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
#pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, new_tab_index):
old_tab: Page = self.currentWidget()
focus_widget = QApplication.focusWidget()
widget_index = old_tab.widgets_map.get_first_inv(focus_widget) if focus_widget else None
self.setCurrentIndex(new_tab_index)
new_tab: Page = self.currentWidget()
if new_tab is not None and widget_index is not None:
corresponding_widget: QWidget = new_tab.widgets_map[widget_index]
corresponding_widget.setFocus(True)
# Move scrollbar to the corresponding position
if hasattr(old_tab, 'scrollbar'):
# Tabs are identical so new_tab must have scrollbar as well
old_y = old_tab.scrollbar.verticalScrollBar().value()
scrollbar: QScrollArea = new_tab.scrollbar
scrollbar.verticalScrollBar().setValue(old_y)
How does one use a QFileSystemModel to populate several QComboBox with subdirectories?
I have built a project management tool that allows me to create and manage my projects. I am currently using a combination of os.listdir and json to populate and validate my QComboboxes. But I am trying to learn a more modelview approach with QFileSystemModel.
So this is what I have:
class FileSystemModel(QW.QFileSystemModel):
def __init__(self, root, parent=None):
QW.QFileSystemModel.__init__(self, parent)
self.root = root
self.rootIndex = self.setRootPath(root)
class Window(QW.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.init()
def init(self):
layout = QW.QVBoxLayout()
self.cbox = QW.QComboBox()
self.cbox2 = QW.QComboBox()
self.model = FileSystemModel("C:\\projects\\")
self.cbox.setModel(self.model)
self.cbox2.setModel(self.model)
self.cbox.setRootModelIndex(self.model.rootIndex)
self.cbox.currentIndexChanged.connect(self._indexChanged)
layout.addWidget(self.cbox)
layout.addWidget(self.cbox2)
self.setLayout(layout)
def _indexChanged(self):
row = self.sender().currentIndex()
index = self.sender().rootModelIndex().child(row, 0)
self.cbox2.setRootModelIndex(index)
def main():
app = QW.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I was attempting to repopulate the cbox2 using the index from cbox, but with my code it doesn't seem to work - it just stays empty.
Okay here is modified version of what you had:
from sys import exit as sysExit
from PyQt5.QtCore import QDir, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QFileSystemModel, QHBoxLayout, QComboBox
class SysDirModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
class SysFileModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
def ResetPath(self, DirPath):
self.setRootPath(DirPath)
self.RootIndex = self.index(DirPath)
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(150, 150, 450, 100)
# If you use forward slash this works in Windows as well and it is cleaner
self.SysDirs = SysDirModel('C:/projects/')
self.SysFils = SysFileModel('C:/projects/')
# Setup first ComboBox
self.cbxDirs = QComboBox()
self.cbxDirs.setMinimumWidth(200)
self.cbxDirs.setModel(self.SysDirs)
self.cbxDirs.setRootModelIndex(self.SysDirs.RootIndex)
# This sends a Signal to a predefined Slot
self.cbxDirs.currentIndexChanged.connect(self.IndexChanged)
self.cbxFiles = QComboBox()
self.cbxFiles.setMinimumWidth(200)
self.cbxFiles.setModel(self.SysFils)
self.cbxFiles.setRootModelIndex(self.SysFils.RootIndex)
HBox = QHBoxLayout()
HBox.addWidget(self.cbxDirs)
HBox.addStretch(1)
HBox.addWidget(self.cbxFiles)
self.setLayout(HBox)
# This is the receiver of a Signal (aka Slot) so it ought to be used as such
#pyqtSlot(int)
def IndexChanged(self, RowIdx):
# Get your Current DirPath based on the Selected Value
index = self.cbxDirs.rootModelIndex().child(RowIdx, 0)
DirPath = self.cbxDirs.model().filePath(index)
# Reset what ComboBox 2's Model and what it is looking at
self.cbxFiles.clear()
self.SysFils.ResetPath(DirPath)
self.cbxFiles.setModel(self.SysFils)
if __name__ == '__main__':
MainThred = QApplication([])
MainGui = MainWindow()
MainGui.show()
sysExit(MainThred.exec_())
I have a Qmenu that I am creating by loading a list with Qsettings and I am trying to be able to remove items from the menu by loading the list in a QListQWidget and deleting the selected items. Currently I am able to delete the menu items in the list widget and it removes them from qsettings as well but I can't figure out how to remove the items from the menu without restarting. I have tried various things such as removeAction etc but haven't been able to figure it out.
Here is my code:
import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
QApplication, QAction, QMenu, QListWidgetItem, \
QListWidget, QGridLayout
class MainWindow(QWidget):
settings = QtCore.QSettings('test_org', 'my_app')
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout()
self.menu_btn = QPushButton()
self.menu = QMenu()
self.add_menu = self.menu.addMenu("Menu")
self.menu_btn.setMenu(self.menu)
self.open_list_btn = QPushButton('open list')
self.load_items = self.settings.value('menu_items', [])
for item in self.load_items:
self.action = QAction(item[0], self)
self.action.setData(item)
self.add_menu.addAction(self.action)
self.action.triggered.connect(functools.partial(self.menu_clicked, self.action))
self.layout.addWidget(self.menu_btn)
self.layout.addWidget(self.open_list_btn)
self.setLayout(self.layout)
self.open_list_btn.clicked.connect(self.open_window)
def open_window(self):
self.create_menu_item = List()
self.create_menu_item.show()
def menu_clicked(self, item):
itmData = item.data()
print(itmData)
class List(QWidget):
settings = QtCore.QSettings('test_org', 'my_app')
def __init__(self, parent=None):
super(List, self).__init__(parent)
self.menu_items = self.settings.value('menu_items', [])
self.layout = QGridLayout()
self.list = QListWidget()
self.remove_btn = QPushButton('Remove')
self.layout.addWidget(self.list, 1, 1, 1, 1)
self.layout.addWidget(self.remove_btn, 2, 1, 1, 1)
self.setLayout(self.layout)
self.remove_btn.clicked.connect(self.remove_items)
for item in self.menu_items:
self.item = QListWidgetItem()
self.item.setText(str(item[0]))
self.list.addItem(self.item)
def remove_items(self):
self.menu_items = self.settings.value('menu_items', [])
del self.menu_items[self.list.currentRow()]
self.settings.setValue('menu_items', self.menu_items)
listItems = self.list.selectedItems()
if not listItems: return
for item in listItems:
self.list.takeItem(self.list.row(item))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Does anyone have any ideas?
EDIT:
This is the structure of the list in QSettings. I load the menu with this and I load the QlistWidget with this. I am trying to get the menu to remove the items as well when I remove them for the QListWidget.
mylist = ['item_name',['itemdata1', 'itemdata2', 'itemdata3'],
'item_name2',['itemdata1', 'itemdata2', 'itemdata3'],
'item_name3',['itemdata1', 'itemdata2', 'itemdata3']]
I think the data structure that you are using is incorrect because when I execute your code it generates twice as many QActions, the structure I propose is a dictionary where the keys are the name of the QAction and the value of the list of data:
{
'item0': ['itemdata00', 'itemdata01', 'itemdata02'],
'item1': ['itemdata10', 'itemdata11', 'itemdata12'],
...
}
To build the initial configuration use the following script:
create_settings.py
from PyQt5 import QtCore
if __name__ == '__main__':
settings = QtCore.QSettings('test_org', 'my_app')
d = {}
for i in range(5):
key = "item{}".format(i)
value = ["itemdata{}{}".format(i, j) for j in range(3)]
d[key] = value
settings.setValue('menu_items', d)
print(d)
settings.sync()
On the other hand I think that the widget that you want to handle the destruction of QActions should take over the corresponding QMenu as I show below:
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
menu_btn = QtWidgets.QPushButton()
open_list_btn = QtWidgets.QPushButton('open list')
layout.addWidget(menu_btn)
layout.addWidget(open_list_btn)
menu = QtWidgets.QMenu()
menu_btn.setMenu(menu)
self.menu_manager = MenuManager("menu_items", "Menu")
menu.addMenu(self.menu_manager.menu)
self.menu_manager.menu.triggered.connect(self.menu_clicked)
open_list_btn.clicked.connect(self.menu_manager.show)
def menu_clicked(self, action):
itmData = action.data()
print(itmData)
class MenuManager(QtWidgets.QWidget):
def __init__(self, key, menuname, parent=None):
super(MenuManager, self).__init__(parent)
self.settings = QtCore.QSettings('test_org', 'my_app')
self.key = key
self.layout = QtWidgets.QVBoxLayout(self)
self.listWidget = QtWidgets.QListWidget()
self.remove_btn = QtWidgets.QPushButton('Remove')
self.layout.addWidget(self.listWidget)
self.layout.addWidget(self.remove_btn)
self.remove_btn.clicked.connect(self.remove_items)
self.menu = QtWidgets.QMenu(menuname)
load_items = self.settings.value(self.key, [])
for name, itemdata in load_items.items():
action = QtWidgets.QAction(name, self.menu)
action.setData(itemdata)
self.menu.addAction(action)
item = QtWidgets.QListWidgetItem(name)
item.setData(QtCore.Qt.UserRole, action)
self.listWidget.addItem(item)
def remove_items(self):
for item in self.listWidget.selectedItems():
it = self.listWidget.takeItem(self.listWidget.row(item))
action = it.data(QtCore.Qt.UserRole)
self.menu.removeAction(action)
self.sync_data()
def sync_data(self):
save_items = {}
for i in range(self.listWidget.count()):
it = self.listWidget.item(i)
action = it.data(QtCore.Qt.UserRole)
save_items[it.text()] = action.data()
self.settings.setValue(self.key, save_items)
self.settings.sync()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I got it figured out. I am not sure of a better way but I did it using object names.
In the MainWindow I set objectNames to self.action using the first item of each list in the list of lists inside the for loop like this:
self.action.setObjectName(item[0])
Then I created this function in the MainWindow class:
def remove_menu_item(self, value):
self.add_menu.removeAction(self.findChild(QAction, value))
Then I added this:
w.remove_menu_item(item.text())
To the remove function in the List class to get the same first item in the list of lists which is now the objectName for the QActions.
Recently, I am working on a code completion demo. I want to create a tooltip association with items in the popup(). The tooltip shows some information queried by the item that it is associated with when users select one item in the popup listview. I tried currentCompletion() to get the item content, it only returned the first completion for one time. How to fix this?
Here is my application
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def get_data(model):
# Searching through the Doc
# Result shows in a dictionary structure
result = {"alpha": 0, "apple": 1, "agree": 2}
icon_address = ['..\..\Src\Img\A.png', '..\..\Src\Img\E.png','..\..\Src\Img\O.png']
for cmd, value in result.items():
item = QStandardItem(cmd)
item.setIcon(QIcon(icon_address[value]))
model.insertRow(0, item)
class CodeAC:
def __init__(self, input_line):
self.completer = QCompleter()
input_line.setCompleter(self.completer)
self.model = QStandardItemModel()
def active_script(self):
get_data(self.model)
self.completer.setModel(self.model)
def tip_balloon(self):
key = self.completer.currentRow()
print(key)
Here is my Main:
from Src.Extention.src.code_ac import *
import sys
import ctypes
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.extention = None
self.entry = QLineEdit(self)
def init_main(self):
self.setGeometry(600,600,800,600)
self.setWindowTitle('VISA Communication')
self.setWindowIcon(QIcon('..\..\Src\Img\icon.png'))
self.show()
def active_extention(self):
self.extention = CodeAC(self.entry)
self.extention.active_script()
if __name__ == '__main__':
app = QApplication(sys.argv)
root = MainWindow()
root.init_main()
root.active_extention()
sys.exit(app.exec_())
Print only give 0, even I select the second completion. Here is the screen shoot
You must use activated signal of QCompleter and modify the tip_balloon:
[...]
self.completer.setModel(self.model)
self.completer.activated.connect(self.tip_balloon)
def tip_balloon(self, text):
print(text)