I have a QTextBrowser() object:
self.PAddressLink = QTextBrowser()
I need to click on a link placed on this QTextBrowser, and it should open a new dialog box.
self.PAddressLink.setHtml("<html><body><a href=#>+Add Permanent Address</a></body></html>")
I can open the new window with the below code anyhow:
self.PAddressLink.anchorClicked.connect(self.AddPAddress) #self.AddPAddress is the method of displaying a dialog box.
But I need to know if I can place the self.AddPAddress in the href and avoid using the below extra statement:
self.PAddressLink.anchorClicked.connect(self.AddPAddress) #self.AddPAddress
Assuming all the methods are defined on the same object (e.g. self), you could set the method names in the href attribute:
self.PAddressLink.setHtml('...')
self.PAddressLink.anchorClicked.connect(self.handleLink)
and then use getattr to call the method:
def handleLink(self, url):
if url.scheme():
# handle normal urls here if necessary...
else:
getattr(self, url.toString())()
Here's a complete demo using both QLabel and QTextBrowser:
from PyQt5 import QtCore, QtGui, QtWidgets
html = """
<p>Url Link</p>
<p>Method Link</p>
"""
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel(html)
self.browser = QtWidgets.QTextBrowser()
self.browser.setOpenLinks(False)
self.browser.setHtml(html)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.browser)
layout.addWidget(self.label)
self.label.linkActivated.connect(self.handleLink)
self.browser.anchorClicked.connect(self.handleLink)
def handleLink(self, url):
url = QtCore.QUrl(url)
if url.scheme():
# handle real urls
QtGui.QDesktopServices.openUrl(url)
else:
# handle methods
getattr(self, url.toString())()
def myMethod(self):
QtWidgets.QMessageBox.information(self, 'Test', 'Hello World!')
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
app.exec()
Most likely not. Atleast not any easy way. You'd be just reimplementing the signals and slots system most likely.
Just as with buttons, you have to connect the click signal to a slot. That's how it is designed to work.
Related
Id like to ask how to connect 2 different classes. I have the following codes in 2 classes (2 classes because i have created 2 different interface. 1 is the QMainwindow and the other 1 is QWidget).
Here's the code:
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.ui = Ui_Inv_Search()
self.ui.setupUi(self.window)
self.window.show()
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.hide()
MainWindow.setEnabled(True)
The idea is when SEARCH button is clicked in MainWindow, Inv_Search will pop up while MainWindow will be disabled. I have done this part correctly. When CLOSE button is clicked, Inv_Search will be hide and MainWindow will be enabled. However, when CLOSE button is clicked, nothing happened. And there is no error at all.
UPDATE
I was able to successfully do what I wanted. Here's the changes I did. Let me know if this is fine or can be better. Nevertheless, this code works.
class MainWindow(QMainWindow, Ui_MainWindow):
Inv_Search.show() #called to connect to the new window. This is the key since what i previously did only call the ui, but not connect it to the class itself.
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.setupUi(self)
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.close() # I cant figure out how can I use the self but this works fine
MainWindow.setEnabled(True)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
MainWindow = MainWindow()
Inv_Search = Inv_Search() #added this one
#ui = Ui_MainWindow()
#ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
When you call Inv_Search.hide() and MainWindow.setEnabled(True) in close_inv_search_form, you call the methods on the class itself not on the instance, which is what you actually have to.
from PyQt5.QtCore import qApp
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.window.ui = Ui_Inv_Search() # You have to set the attributes of the
self.window.ui.setupUi(self.window) # "window" not the instance of MainWindow
self.window.show()
self.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
self.hide() # Use the method "hide" on the instance
qApp.setEnabled(True) # Use the method "setEnabled" on the instance
if __name__ == "__main__" :
app = QApplication()
main = MainWindow() # This is the instance that can be referred to by qApp
main.exec_()
I am assuming that there is more code and a .ui file somewhere. It looks like this line
Inv_Search.hide()
should be changed to
self.hide()
Also, I think that because you need to call the method on the instance, not the class.
self.ui = Ui_Inv_Search()
should probably be
self.ui = Inv_Search()
You are doing a similar thing with MainWindow. It's a little more difficult here, because you will need to have an instance of MainWindow stored somewhere accessible. Although you may be able to access the MainWindow instance through QtWidget.parentWidget, in Python I prefer to just pass the instance to the constructor. So
def __init__(self, mainWindow):
self.mainWindow = mainWindow
# ... all the other stuff too
as your Inv_Search constructor, and
self.ui = Inv_Search(self)
# notice the new ^ argument
in your MainWindow constructor. And then
self.mainWindow.setEnabled(True)
in your class method. Also, your argument signature is wrong for the clicked signal. Use
def close_inv_search_form(self, checked=None):
# Need this ^ argument, even if you don't use it.
In reality, it seems like the functionality you are trying to accomplish is best suited for a modal dialog, such as that provided by QDialog, which will natively handle many of the effect I think you are looking for.
I need to generate a custom popup input window triggered by clicking a QPushButton in my app (via clicked). It needs to get several inputs from the user of different types and then return them to the calling function inside the main window app. I have found built in functions such as QInputDialog that can do this for single specific inputs, but I can't figure out how to do this in the case of a popup that asks for several inputs of different types at once (preferably in a window designed in Qt Designer). Does anyone know how to do this?
import sys
import os
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5 import uic
path = os.path.dirname(__file__) #uic paths from itself, not the active dir, so path needed
qtCreatorFile = "NAME.ui" #Ui file name, from QtDesigner
Ui_MainWindow, QtBaseClass = uic.loadUiType(path + qtCreatorFile) #process through pyuic
class MyApp(QMainWindow, Ui_MainWindow): #gui class
def __init__(self):
#Set up the gui via Qt
super(MyApp, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.add_button.clicked.connect(self.add_row) #add_button is QPushButton
def add_row(self):
data1, data2, data3 = #popup form to get data (types are not the same)
#do stuff with data
pass
#start app
if __name__ == "__main__":
app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
window = MyApp()
window.show()
sys.exit(app.exec_())
There is no single solution but I will give you a guide to do what you want.
If you want to get a widget with the behavior of QInputDialog you must first choose the right template, in this case a good option is Dialog with Buttons Bottom or Dialog with Buttons Right, add the components you want, position it, etc.
Then as you show your code you create a class that inherits from QDialog and then create a method where you get the results but to do so do not use show() but exec_()
path = os.path.dirname(__file__)
qtCreatorFile = "some_dialog.ui"
Ui_Dialog, _ = uic.loadUiType(os.path.join(path,qtCreatorFile))
class CustomDialog(QDialog, Ui_Dialog):
def __init__(self):
super(CustomDialog, self).__init__()
self.setupUi(self)
# set initials values to widgets
def getResults(self):
if self.exec_() == QDialog.Accepted:
# get all values
val = self.some_widget.some_function()
val2 = self.some_widget2.some_another_function()
return val1, val2, ...
else:
return None
And then use it in your function:
class MyApp(QMainWindow, Ui_MainWindow): #gui class
def __init__(self):
#Set up the gui via Qt
super(MyApp, self).__init__()
self.setupUi(self)
self.add_button.clicked.connect(self.add_row) #add_button is QPushButton
def add_row(self):
w = CustomDialog()
values = w.getResults()
if values:
data1, data2, data3 = values
In my Qt application I'm using the QCalendarWidget and I would like to get notified when the mouse enters a new cell of the calendar. I know that the QCalendarWidget is using a QTableView internally which inherits from QAbstractItemView and this has an entered signal:
This signal is emitted when the mouse cursor enters the item specified
by index. Mouse tracking needs to be enabled for this feature to work.
I tried to receive the signal with following code:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyCalendar:
def __init__(self):
app = QApplication(sys.argv)
window = QMainWindow()
cal = QCalendarWidget(window)
window.resize(320, 240)
cal.resize(320, 240)
table = cal.findChild(QTableView)
table.setMouseTracking(True)
table.entered.connect(self.onCellEntered)
window.show()
sys.exit(app.exec_())
def onCellEntered(self, index):
print("CellEntered")
if __name__ == "__main__":
window = MyCalendar()
But my callback function is never called. Do you have any ideas why?
The QCalendarWidget class uses a custom table-view which bypasses the normal mouse-event handlers - so the enetered signal never gets emitted. However, it is possible to work-around that by using an event-filter to emit a custom signal that does the same thing.
Here is a demo script that implements that:
import sys
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
cellEntered = QtCore.pyqtSignal(object)
def __init__(self):
super(Window, self).__init__()
self.calendar = QtGui.QCalendarWidget(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.calendar)
self._table = self.calendar.findChild(QtGui.QTableView)
self._table.setMouseTracking(True)
self._table.installEventFilter(self)
self._index = None
self.cellEntered.connect(self.handleCellEntered)
def eventFilter(self, source, event):
if source is self._table:
if event.type() == QtCore.QEvent.MouseMove:
index = QtCore.QPersistentModelIndex(
source.indexAt(event.pos()))
if index != self._index:
self._index = index
self.cellEntered.emit(QtCore.QModelIndex(index))
elif event.type() == QtCore.QEvent.Leave:
self._index = None
return super(Window, self).eventFilter(source, event)
def handleCellEntered(self, index):
print(index.row(), index.column())
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
I've investigated a bit and I think I know why this happens.
QCalendarWidget creates a private subclass of QTableView called QCalendarView and instantiates it as a child of itself. (This instance is called qt_calendar_calendarview.)
If you look at QCalendarView's code (Qt 5), you'll see this:
void QCalendarView::mouseMoveEvent(QMouseEvent *event)
{
[...]
if (!calendarModel) {
QTableView::mouseMoveEvent(event);
return;
}
[...]
}
This means only if there is no calendarModel, the superclasses mouseMoveEventis called which is responsible for emitting the entered signal. All of this is an implementation detail of `QCalendarWidget, so it's probably best not to rely on any of this anyway.
So for a way around this, I'm not sure what the best approach is. You'll have to catch the event before it gets to the table. This can be done using QObject.installEventFilter() or re-implementing QWidget.mouseMoveEvent(), but then you don't get the model index directly. You could probably use QAbstractItemView.indexAt() for this purpose.
I have a QTextEdit widget layed out on the left and a QTextBrowser on the right,
I am looking for a way to do the following:
the user select a some text
a function is triggered by this event
the selected text is processed
the processed text is displayed on the right
I have googled for it but didn't see relevant results.
You can use the selectionChanged signal to trigger the function, and then retrieve the selected text via the textCursor. The processed text can be displayed by using either setPlainText, or, if you want to use markup, setHtml. But note that QTextBrowser only supports a limited subset of html/css.
Here's a demo script that shows how to put it all together:
from PySide import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.edit = QtGui.QTextEdit(self)
self.edit.selectionChanged.connect(self.handleSelectionChanged)
self.browser = QtGui.QTextBrowser(self)
layout = QtGui.QHBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.browser)
def handleSelectionChanged(self):
text = self.edit.textCursor().selectedText()
# process text here...
self.browser.setPlainText(text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 300)
window.show()
sys.exit(app.exec_())
I couldn't understand the connectSlotsByName() method which is predominently used by pyuic4.. As far the class is single in a PyQt file it's ok since we can use self which will be associated with a single object throughout.. But when we try to use various classes from different files the problem and the need to use connectSlotsByName() arises.. Here's what i encountered which is weird..
I created a stacked widget..
I placed my first widget on it.. It
has a button called "Next >".
On clicking next it hides the current
widget and adds another widget which has the "click me" button..
The problem here is the click event for "click me" button in second is not captured.. It's a minimal example that i can give for my original problem.. Please help me..
This is file No.1..(which has the parent stacked widget and it's first page). On clicking next it adds the second page which has "clickme" button in file2..
from PyQt4 import QtCore, QtGui
import file2
class Ui_StackedWidget(QtGui.QStackedWidget):
def __init__(self,parent=None):
QtGui.QStackedWidget.__init__(self,parent)
self.setObjectName("self")
self.resize(484, 370)
self.setWindowTitle(QtGui.QApplication.translate("self", "stacked widget", None, QtGui.QApplication.UnicodeUTF8))
self.createWidget1()
def createWidget1(self):
self.page=QtGui.QWidget()
self.page.setObjectName("widget1")
self.pushButton=QtGui.QPushButton(self.page)
self.pushButton.setGeometry(QtCore.QRect(150, 230, 91, 31))
self.pushButton.setText(QtGui.QApplication.translate("self", "Next >", None, QtGui.QApplication.UnicodeUTF8))
self.addWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.showWidget2)
def showWidget2(self):
self.page.hide()
obj=file2.widget2()
obj.createWidget2(self)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Ui_StackedWidget()
ui.show()
sys.exit(app.exec_())
Here's file2
from PyQt4 import QtGui,QtCore
class widget2():
def createWidget2(self,parent):
self.page = QtGui.QWidget()
self.page.setObjectName("page")
self.parent=parent
self.groupBox = QtGui.QGroupBox(self.page)
self.groupBox.setGeometry(QtCore.QRect(30, 20, 421, 311))
self.groupBox.setObjectName("groupBox")
self.groupBox.setTitle(QtGui.QApplication.translate("self", "TestGroupBox", None, QtGui.QApplication.UnicodeUTF8))
self.pushButton = QtGui.QPushButton(self.groupBox)
self.pushButton.setGeometry(QtCore.QRect(150, 120, 92, 28))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText(QtGui.QApplication.translate("self", "Click Me", None, QtGui.QApplication.UnicodeUTF8))
self.parent.addWidget(self.page)
self.parent.setCurrentWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.printMessage)
def printMessage(self):
print("Hai")
Though in both the widgets(i mean pages)
QtCore.QMetaObject.connectSlotsByName(self.page)
the clicked signal in second dialog isn't getting processed. Thanks in advance.. Might be a beginner question..
A better question is "Why not just use new-style signals and slots?". They're much simpler and don't require any weird naming conventions:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtGui.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtGui.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._printMessage)
#QtCore.pyqtSlot()
def _printMessage(self):
print("Hai")
if __name__ == "__main__":
app = QtGui.QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
At first, here is the minimal working example:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class widget2(QtGui.QWidget):
def __init__(self, args):
self.app = MainApp(args)
QtGui.QWidget.__init__(self)
self.setObjectName('I')
self._layout = QtGui.QVBoxLayout(self)
self.setLayout(self._layout)
self.pushButtoninWidget2 = QtGui.QPushButton(self)
self.pushButtoninWidget2.setObjectName("pushButtoninWidget2")
self.pushButtoninWidget2.setText('Click NOW!')
self._layout.addWidget(self.pushButtoninWidget2)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.pyqtSlot()
def on_pushButtoninWidget2_clicked(self):
print("Hai")
class MainApp(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
if __name__ == "__main__":
main = widget2(argv)
main.show()
exit(main.app.exec_())
When you trying to connect slots by name, you must give proper names to the slots and then someone (moc, uic, or you by calling connectSlotsByName) must connect them. Proper name for such a slot is: "on_PyQtObjectName_PyQtSignalName".
Note, that, if I'd omitted #QtCore.pyqtSlot() in the example, slot would be executed once for every appropriate overload (twice in this case).
You DO need to call connectSlotsByNames directly, cause there is no moc, which do it for you when you use QT in C++, and you do not use uic and .ui file. If you want to connect slots implicitly (I'm always doing so, except slots, connected directly in .ui), you'd better use more pytonish syntaxe: button.clicked.connect(self._mySlot).
And take a look at https://riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#connecting-slots-by-name
You do not need to call connectSlotsByName(), just remove those lines.
In file2, calling QtCore.QMetaObject.connectSlotsByName(self.page) tries to do this:
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL('clicked()'), self.on_pushButton_clicked())
That will not work for you since self.on_pushBotton_clicked() slot is not defined.
I find it is easiest to create your own connections in PyQt... I recommend removing the calls to connectSlotsByName from your both classes... you do not need it.
Also, your wdiget1 class should set the name of it's pushButton (preferably something other then "pushButton" to avoid confusion with the button in widget2).
Thank you so much jcoon for your reply.. But after a very long time banging my head against the wall i found the solution..
The problem was..
self.obj=test_reuse_stacked1.widget2()
self.obj.createWidget2(self)
instead of obj..
Here is #MarkVisser's QT4 code updated to QT5:
from sys import argv, exit
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtWidgets.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtWidgets.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._print_message)
#QtCore.pyqtSlot()
def _print_message(self):
print("Hai")
if __name__ == "__main__":
app = QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
Another minimal working example with Qt for Python aka PySide2/6.
Key ingredients:
widget to connect MUST have .setObjectName
function to connect MUST be decorated with #QtCore.Slot()
both objects (function AND widget) MUST be members of passed object (self here)
from PySide2 import QtCore, QtWidgets
# or from PySide6 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton(self)
self.button.setObjectName('button')
self.button.setText('Click Me!')
layout.addWidget(self.button)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.Slot()
def on_button_clicked(self):
print(f'Hai from {self.sender()}')
if __name__ == '__main__':
app = QtWidgets.QApplication([])
main = Widget()
main.show()
app.exec_()
I couldn't get mit any smaller really 🤔