Issue
Grееtings аll. I am new to this site, so go easy on me.
I am building a program in python using PyQt5 for the interface. I currently have, among other things, a QListWidget (list window) into which I insert a number of QWidgets (list items) through a function during the running of the program. I have implemented an eventFilter
by subclassing QObject, and I use it to differentiate between left and right click, and from that I send to the controller class to handle one or the other click accordingly.
I have made it so that when right-clicked on a list item, a context menu appears. However, I also need a context menu to appear when the list window is clicked (not on a list item). The problem which occurs is that when right-clicked on a list item, both the list item context menu and list window context menu appear. This must be because the event filter recognises the click as occurring within the list window, because it is occurring on a list item, which is within the list window. What I need is that when right-clicked on a list item, only its context menu appears, and similarly for the list window, when right-clicked outside the list items.
I have tried checking if source
equals the widget where the event appeared, but it seems to recognise both widgets' events independently and if I gate the call to the handler with an if
condition, one of the handlers never receives a call. I have searched around the web and this site, but I have found nothing of use. Perhaps it is due to me not being a native speaker and so not being able to phrase things correctly (you can see how clunky my phrasing is).
Below follows some code extracted from my program. Note that anything irrelevant has been cut away to make for a minimal example. For your convenience, I have also merged the GUI files into one and did the same for the control files. I have tested this minimal example and it reproduces the problem. It could not get smaller, so if you deem the code listed below too long, notify me and I can reupload it to GitHub if it is allowed to show the minimal example that way instead of putting code into the question directly.
custom_classes.py
:
from PyQt5.QtCore import Qt, QEvent, QObject
class MyFilter(QObject):
def __init__(self, parent, ctrl):
super().__init__(parent)
self._parent = parent
self.ctrl = ctrl
self.parent.installEventFilter(self)
@property
def parent(self):
return self._parent
def eventFilter(self, source, event):
if event.type() == QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton:
self.ctrl.handle_left_click()
elif event.button() == Qt.RightButton:
self.ctrl.handle_right_click(event)
return super().eventFilter(source, event)
gui.py
:
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QListWidget
from PyQt5.QtWidgets import QScrollArea
from PyQt5.QtCore import Qt
class MainFrame(QWidget):
def __init__(self):
super().__init__()
self.main_layout = QHBoxLayout()
self.setLayout(self.main_layout)
class ListItemFrame(QWidget):
def __init__(self):
super().__init__()
self.main = QHBoxLayout()
self.main.setContentsMargins(0,0,0,0)
self.name_layout = QHBoxLayout()
self.name_layout.setContentsMargins(0,0,0,0)
self.main.addLayout(self.name_layout)
self.name = QLabel("")
self.name.setMaximumHeight(20)
self.name_layout.addWidget(self.name)
self.setLayout(self.main)
class ListFrame(QListWidget):
def __init__(self):
super().__init__()
self.main = QVBoxLayout()
self.scroll_widget = QScrollArea()
self.scroll_widget.setWidgetResizable(True)
self.scroll_layout = QVBoxLayout()
self.scroll_layout.setAlignment(Qt.AlignTop)
self.scroll_layout_widget = QWidget()
self.scroll_layout_widget.setLayout(self.scroll_layout)
self.scroll_widget.setWidget(self.scroll_layout_widget)
self.main.addWidget(self.scroll_widget)
self.setLayout(self.main)
ctrl.py
:
from PyQt5.QtWidgets import QMenu
from gui import ListFrame, ListItemFrame
from custom_classes import MyFilter
class Controller:
def __init__(self, ui, app):
self.ui = ui
self.app = app
self.list_ = ListControl(self)
class ListControl:
def __init__(self, ctrl):
self.ctrl = ctrl
self.ui = ListFrame()
self.the_list = self.get_list() #list of stuff
self.item_list = [] #list of list items
self.ctrl.ui.main_page.main_layout.addWidget(self.ui)
self.index = self.ctrl.ui.main_page.main_layout.count() - 1
self.filter = MyFilter(self.ui, self)
self.show_list()
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
one_action = menu.addAction("Something!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.ctrl.ui.close()
elif action == one_action:
self.something()
def something(self):
print("Something!")
def show_list(self):
for info in self.the_list:
item = ListItem(self, info)
self.item_list.append(item)
def get_list(self):
return [x for x in "qwertzuiopasdfghjklyxcvbnm"]
class ListItem:
def __init__(self, main, info):
self.main = main
self.info = info*10
self.ui = ListItemFrame()
self.filter = MyFilter(self.ui, self)
self.set_ui()
self.add_to_ui()
self.main.ui.scroll_layout.addWidget(self.ui)
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
item_action = menu.addAction("Hello!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.main.ctrl.ui.close()
elif action == item_action:
self.hello()
def hello(self):
print(f"Hello! I am {self.info}")
def set_ui(self):
self.ui.name.setText(self.info)
def add_to_ui(self):
self.main.ui.scroll_layout.insertWidget(
self.main.ui.scroll_layout.count() - 1, self.ui
)
main.py
:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QStackedLayout
from PyQt5.QtWidgets import QWidget
from gui import MainFrame
from ctrl import Controller
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("minimal example")
self.stacked = QStackedLayout()
self.main_page = MainFrame()
self.stacked.addWidget(self.main_page)
self.setLayout(self.stacked)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = Window()
window.show()
c = Controller(window, app)
sys.exit(app.exec())
To reiterate, the context menu appears for both the list item and the list window when a list item is right-clicked. What I need is for it to appear only for the list item if a list item is right-clicked.
Edit: seems the site bit off a part of my introduction. Readded it!
Solution
this is probably not the best way to do it but it works. You just create a global variable, for example list_element_clicked
and when you click "hello" (of course not quit because you are going to exit the window and there is no point) you set that variable to True. If that variable is False, you show the ListControl
context menu, and if not, you set that variable to True, so next time if you click on your ListControl
it will appear, and if you click on ListItem
it will not.
Finally there is an extra case, if you don't click anywhere after clicking on ListItem
, nothing will happen (the ListControl
is not shown and the variable is not changed) so everything will work perfectly next time.
So here is the code:
ctrl.py
:
from PyQt5.QtWidgets import QMenu
from gui import ListFrame, ListItemFrame
from custom_classes import MyFilter
list_element_clicked = False
class Controller:
def __init__(self, ui, app):
self.ui = ui
self.app = app
self.list_ = ListControl(self)
class ListControl:
def __init__(self, ctrl):
self.ctrl = ctrl
self.ui = ListFrame()
self.the_list = self.get_list() #list of stuff
self.item_list = [] #list of list items
self.ctrl.ui.main_page.main_layout.addWidget(self.ui)
self.index = self.ctrl.ui.main_page.main_layout.count() - 1
self.filter = MyFilter(self.ui, self)
self.show_list()
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
global list_element_clicked
if(list_element_clicked == False):
self.show_options(event)
else:
list_element_clicked = False
def show_options(self, event):
menu = QMenu()
one_action = menu.addAction("Something!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.ctrl.ui.close()
elif action == one_action:
self.something()
def something(self):
print("Something!")
def show_list(self):
for info in self.the_list:
item = ListItem(self, info)
self.item_list.append(item)
def get_list(self):
return [x for x in "qwertzuiopasdfghjklyxcvbnm"]
class ListItem:
def __init__(self, main, info):
self.main = main
self.info = info*10
self.ui = ListItemFrame()
self.filter = MyFilter(self.ui, self)
self.set_ui()
self.add_to_ui()
self.main.ui.scroll_layout.addWidget(self.ui)
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
item_action = menu.addAction("Hello!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.main.ctrl.ui.close()
elif action == item_action:
global list_element_clicked
list_element_clicked = True
self.hello()
def hello(self):
print(f"Hello! I am {self.info}")
def set_ui(self):
self.ui.name.setText(self.info)
def add_to_ui(self):
self.main.ui.scroll_layout.insertWidget(
self.main.ui.scroll_layout.count() - 1, self.ui
)
Answered By - WhoKnowsMe
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.