I am trying to code an application that will allow the user to view a list of Tag IDs, as well as its description, and allow the user to check off each Tag ID that they would like to import data from. At this point I am working on developing the UI only.
The code below worked and would show the application window until I added the itemChanged function & connection. Now, when I run this code, only the print statement from the new function will show. The window never shows and the entire application promptly exits (see image for outcome of running script).
Additionally, you'll notice that we get the checkState of each type of item - I only want the checkState of the Tag ID.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTableView, QHeaderView, QVBoxLayout, QAbstractItemView
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class myApp(QWidget):
def __init__(self):
super().__init__()
self.resize(1000, 500)
mainLayout = QVBoxLayout()
tagIDs = ('Tag_1', 'Tag_2', 'Tag_3', 'Tag_4', 'Tag_5')
descriptions = ('Description_1', 'Description_2', 'Description_3', 'Description_4', 'Description_5')
model = QStandardItemModel(len(tagIDs), 2)
model.itemChanged.connect(self.itemChanged)
model.setHorizontalHeaderLabels(['Tag IDs', 'Description'])
for i in range(len(tagIDs)):
item1 = QStandardItem(tagIDs[i])
item1.setCheckable(True)
item2 = QStandardItem(descriptions[i])
model.setItem(i, 0, item1)
model.setItem(i, 1, item2)
filterProxyModel = QSortFilterProxyModel()
filterProxyModel.setSourceModel(model)
filterProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
filterProxyModel.setFilterKeyColumn(1)
searchField = QLineEdit()
searchField.setStyleSheet('font-size: 20px; height: 30px')
searchField.textChanged.connect(filterProxyModel.setFilterRegExp)
mainLayout.addWidget(searchField)
table = QTableView()
table.setStyleSheet('font-size: 20px;')
table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
table.setModel(filterProxyModel)
table.setEditTriggers(QAbstractItemView.NoEditTriggers)
mainLayout.addWidget(table)
self.setLayout(mainLayout)
def itemChanged(self, item):
print("Item {!r} checkState: {}".format(item.text(), item.checkState()))
def main():
app = QApplication(sys.argv)
myAppControl = myApp()
myAppControl.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Header settings that depend on the model must always be set when a model is set.
Move table.setModel(filterProxyModel) right after the creation of the table or, at least, before table.horizontalHeader().setSectionResizeMode (the vertical setSectionResizeMode() is generic for the whole header and doesn't cause problems).
Related
My code is supposed to add data to a two column table when "add" button is clicked. The problem is that when the "add" button is clicked, only the empty row is being added. Can someone please let me know what is wrong? Below is the part of the code that adds data1 and data2 to a table on the right side of the layout. The function add_entry is where the data is being added.
# Import dependencies
from PyQt5.QtWidgets import (QWidget, QApplication, QTableWidget, QTableWidgetItem,QHBoxLayout, QVBoxLayout, QHeaderView, QPushButton, QDialog,
QLabel, QFileDialog, QMainWindow, QAction, QLineEdit)
from PyQt5.Qt import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtChart import QChart, QChartView, QLineSeries
import sys
import pandas as pd
import math
# ------------------------------------------------------UI-main----------------------------------------------------------------------------------
# Creates a QApplication instance
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.items=0
# Creates table on the left size
self.table_l = QTableWidget()
self.table_l.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# Creates layout object for the right side
self.layoutRight = QVBoxLayout()
# Creates chart widget
self.chartView = QChartView()
# Smooths the edge of the chart
self.chartView.setRenderHint(QPainter.Antialiasing)
# Creates table on the right size
self.table_r = QTableWidget()
self.table_r.setColumnCount(2)
# self.table_r.setRowCount()
self.table_r.setHorizontalHeaderLabels(('Data1', 'Data2'))
self.table_r.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table_r.setMaximumSize(600, 300)
self.lineEditData1 = QLineEdit()
self.lineEditData2 = QLineEdit()
# Create push buttons
self.buttonAdd = QPushButton('Add')
self.buttonClear = QPushButton('Clear')
self.buttonQuit = QPushButton('Quit')
self.buttonAdd.setEnabled(False)
self.layoutRight.setSpacing(10)
self.layoutRight.addWidget(self.table_r, 50)
self.layoutRight.addWidget(QLabel('data1'))
self.layoutRight.addWidget(self.lineEditData1)
self.layoutRight.addWidget(QLabel('data2'))
self.layoutRight.addWidget(self.lineEditData2)
self.layoutRight.addWidget(self.buttonAdd)
self.layout = QHBoxLayout()
self.layout.addWidget(self.table_l, 50)
self.setLayout(self.layout)
self.layout.addLayout(self.layoutRight, 50)
# Connect button to function functions
self.buttonQuit.clicked.connect(lambda:app.quit())
self.buttonAdd.clicked.connect(self.add_entry)
self.buttonClear.clicked.connect(self.reset_table)
self.lineEditData1.textChanged[str].connect(self.check_disable)
self.lineEditData2.textChanged[str].connect(self.check_disable)
def add_entry(self):
Data1 = self.lineEditData1.text()
Data2 = self.lineEditData2.text()
try:
Data1Item = QTableWidgetItem(int(Data1))
Data2Item = QTableWidgetItem(float(Data2))
Data2Item.setTextAlignment(Qt.AlignRight | Qt.AlignCenter)
self.table_r.insertRow(self.items)
self.table_r.setItem(self.items, 0, Data1Item)
self.table_r.setItem(self.items, 1, Data2Item)
self.items +=1
# after passing the item, clear the field by entering an empty string
self.lineEditData1.setText('')
self.lineEditData2.setText('')
except ValueError:
pass
# Creates main window object instance
class MainWindow(QMainWindow):
def __init__(self, widget):
super().__init__()
self.setWindowTitle('test')
self.resize(1200, 1200)
self.menuBar = self.menuBar()
self.fileMenu = self.menuBar.addMenu('File')
# import wind speed data
importAction = QAction('Open File', self)
importAction.setShortcut('Ctrl+O')
# exit action
exitAction = QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(lambda: app.quit())
self.fileMenu.addAction(importAction)
self.fileMenu.addAction(exitAction)
self.setCentralWidget(widget)
if __name__ =='__main__':
# don't auto scale when drag app to a different monitor
#QGuiApplication.setHightDpiScaleFactorRoundingPolicy(Qt.HightDpiScaleFactorRoundingPolicy.PassThrough)
app = QApplication(sys.argv)
w = MyApp()
demo = MainWindow(w)
demo.show()
try:
sys.exit(app.exec())
except SystemExit:
print('Closing window...')
The objective of exceptions is not to hide errors but to know how to prevent them, so they must be as small as possible so as not to hide other errors. In this case, QTableWidgetItem accepts a string as an argument and not numerical values, therefore an exception is thrown preventing the code that adds the items from being executed. The solution is to use the setData() method of the QTableWidgetItem:
def add_entry(self):
data1 = self.lineEditData1.text()
data2 = self.lineEditData2.text()
try:
value1 = int(data1)
value2 = float(data2)
except ValueError:
print("failed conversion")
return
else:
data1_item = QTableWidgetItem()
data1_item.setData(Qt.DisplayRole, value1)
data2_item = QTableWidgetItem()
data2_item.setData(Qt.DisplayRole, value2)
data2_item.setTextAlignment(Qt.AlignRight | Qt.AlignCenter)
row = self.table_r.rowCount()
self.table_r.insertRow(row)
self.table_r.setItem(row, 0, data1_item)
self.table_r.setItem(row, 1, data2_item)
self.lineEditData1.clear()
self.lineEditData2.clear()
I found a similar question on this.
Keep the selection after filtering a QTableView with a QSortFilterProxyModel
But it is about C++ QT, I tried myself many times, but I still did not pull it off in PyQt5 or PySide6?
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTableView, QHeaderView, QVBoxLayout
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class AppDemo(QWidget):
def __init__(self):
super().__init__()
self.resize(1200, 1000)
mainLayout = QVBoxLayout()
# companies = ('Apple', 'Facebook', 'Google', 'Amazon', 'Walmart', 'Dropbox', 'Starbucks', 'eBay', 'Canon')
companies = [f'company_{i}' for i in range(200)]
model = QStandardItemModel(len(companies), 1)
model.setHorizontalHeaderLabels(['Company'])
for row, company in enumerate(companies):
item = QStandardItem(company)
model.setItem(row, 0, item)
filter_proxy_model = QSortFilterProxyModel()
filter_proxy_model.setSourceModel(model)
filter_proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
filter_proxy_model.setFilterKeyColumn(0)
search_field = QLineEdit()
search_field.textChanged.connect(filter_proxy_model.setFilterRegExp)
mainLayout.addWidget(search_field)
table = QTableView()
table.setStyleSheet('font-size: 35px;')
table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setModel(filter_proxy_model)
mainLayout.addWidget(table)
self.setLayout(mainLayout)
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
I found a solution myself. I switched to use a QListWidget instead. But the same logic also can be applied to QListView. Below is the code.
Below is the core part, it is a slot that is connected to a line edit. Doing this way, the efficiency is good especially when you have many items as it doesn' use QListWidget.clear() when lineedit input changes, it just hide the items behind the curtain.
# deal with search file
def onFileTextChanged(self, text):
search_items = self.listWidget.findItems(text, Qt.MatchContains)
for index in range(self.listWidget.count()):
item = self.listWidget.item(index)
if item in search_items:
item.setHidden(False)
else:
item.setHidden(True)
I have a simple app with two QListWidgets
I want to:
drag and drop between them
rearrange the order within them.
The problem: When I attempt to rearrange the order within one of the QListWidgets, the QListItem disappears.
Here is a small example (I'm using python3)
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtCore import Qt
class DragWidget(QtWidgets.QListWidget) :
def __init__(self,parent,total=None) :
super(DragWidget,self).__init__(parent)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
#Want horizontal listwidgets.
self.setFlow(QtWidgets.QListView.Flow.LeftToRight)
#Here's the attempt to configure dragging.
self.setDragEnabled(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
self.setDropIndicatorShown(True)
self.setDefaultDropAction(Qt.MoveAction)
self.viewport().setAcceptDrops(True)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setResizeMode(QtWidgets.QListView.ResizeMode.Adjust)
self.setSpacing(2)
self.setFixedHeight(50)
#An attempt to overload the dragEnterEvent
def dragEnterEvent(self,event) :
#Use the InternalMove if the source = the drop site
if (event.source() is self):
self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
else :
#And regular ol' DragDrop if not.
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
super().dragEnterEvent(event)
class DragDemo(QtWidgets.QDialog) :
def __init__(self,parent=None,*args,**kwargs) :
super().__init__()
layout = QtWidgets.QGridLayout()
groupbox = QtWidgets.QGroupBox("Display Columns")
groupbox.setLayout(layout)
showlist = DragWidget(groupbox)
options = ['type','name','timestamp']
itemlist = []
for option in options :
item = QtWidgets.QListWidgetItem(option,showlist)
itemlist.append(item)
layout.addWidget(showlist,0,0)
hidelist = DragWidget(groupbox)
layout.addWidget(hidelist,1,0)
vlayout = QtWidgets.QVBoxLayout()
vlayout.addWidget(groupbox)
self.setLayout(vlayout)
self.show()
app = QApplication(sys.argv)
demo = DragDemo()
demo.show()
sys.exit(app.exec_())
Who out there can tell me what I'm doing wrong?
Any, and all help is appreciated.
I'm new to python and pyqt. I am am trying to create a matrix of QToolButtons where upon pressing a button, a QDialog pops up for user input (more than one field).
I have a class for a button matrix object and a class for a dialog but can't seem to get a function within the button matrix class initiate an instance of the dialog class / widget.
Can anyone please advise me on what I'm doing wrong?
I have provided the code below:
from PyQt4 import QtGui
from PyQt4.QtGui import QApplication, QWidget, QFormLayout, QInputDialog, QPushButton, QToolButton, QLabel, QVBoxLayout, QHBoxLayout, QLineEdit
class Pixel(object):
def __init__(self, pixel_number, r_value, g_value, b_value):
self.pixel = pixel_number
self.red_value = r_value
self.green_value = g_value
self.blue_value = b_value
class inputdialogdemo(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
#super(inputdialogdemo, self).__init__(parent)
layout = QFormLayout()
self.btn1 = QPushButton("Enter red value")
self.le1 = QLineEdit()
self.btn1.clicked.connect(self.getRed)
layout.addRow(self.btn1,self.le1)
self.btn2= QPushButton("Enter green value")
self.le2 = QLineEdit()
self.btn2.clicked.connect(self.getGreen)
layout.addRow(self.btn1,self.le2)
self.btn3 = QPushButton("Enter blue value")
self.le3 = QLineEdit()
self.btn3.clicked.connect(self.getBlue)
layout.addRow(self.btn3,self.le3)
self.setLayout(layout)
self.setWindowTitle("RGB input dialog ")
def getRed(self):
num, ok = QInputDialog.getText(self, 'Red Input Dialog', 'Enter your name:')
if ok:
self.le1.setText(str(num))
def getGreen(self):
num,ok = QInputDialog.getInt(self,"Green input dualog","enter a number")
if ok:
self.le2.setText(str(num))
def getBlue(self):
num,ok = QInputDialog.getInt(self,"Blue input dualog","enter a number")
if ok:
self.le3.setText(str(num))
class ClusterArray(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
#self.button_layout = QHBoxLayout()
self.button_layout = QtGui.QGridLayout()
self.widget_layout = QtGui.QGridLayout()
for cluster_number in range(1, 15):
for pixel_number in range(1, 5):
button = QToolButton()
button.setText(str(cluster_number) + str(pixel_number))
button.setObjectName(f"Cluster{cluster_number},Pixel{pixel_number}")
button.released.connect(self.button_released)
self.button_layout.addWidget(button, cluster_number, pixel_number)
self.status_label = QLabel('No button clicked')
self.widget_layout.addItem(self.button_layout)
self.widget_layout.addWidget(self.status_label)
self.setLayout(self.widget_layout)
ex = inputdialogdemo()
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
ex = inputdialogdemo()
ex.show()
#i, okPressed = QInputDialog.getInt(self, "Get integer","Percentage:", 28, 0, 100, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = ClusterArray()
widget.show()
# ex = inputdialogdemo()
# ex.show()
sys.exit(app.exec_()
At the moment I've just tried to get my class' function to create an object from some demo code in a class called 'inputdialogdemo'
Also, I would like to keep the inputted values associated with the corresponding button on the matrix somehow. I'm thinking the values should be stored in another object that each button represents. My object would be 'Pixel' that holds red, green and blue values. I have yet to code this functionality. Does this method sound feasible?
The inputdialogdemo instance is correctly created both in the __init__ and in button_released, the problem is that as soon as those function return, the ex instance gets garbage collected: since there is no persistent reference (ex is just a local variable), python automatically deletes it to avoid unnecessary memory consumption.
Since you're needing a dialog, the best solution is to inherit from QDialog instead of QWidget; this has two important benefits: it keeps the dialog modal (it stays on top of other windows and avoid interaction with them) and provides the exec_() method, which does not return until the dialog is closed; then you can add a QDialogButtonBox for standard Ok/Cancel buttons, and connect its accepted and rejected to the accept() and reject() slots of the dialog.
from PyQt4.QtGui import (QApplication, QWidget, QFormLayout, QInputDialog, QPushButton, QToolButton,
QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QDialog, QDialogButtonBox)
class inputdialogdemo(QDialog):
def __init__(self, parent = None):
# ...
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
layout.addRow(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
class ClusterArray(QWidget):
# ...
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
ex = inputdialogdemo()
if ex.exec_():
red = ex.le1.text()
green = ex.le2.text()
blue = ex.le2.text()
Some suggestions:
if you need a numerical value, don't use QLineEdit, but QSpinBox;
to add a layout to another, use setLayout(), not addItem();
to connect to a button click, use the clicked signal, not released;
while special characters are not strictly forbidden for object names, it's usually better to avoid them; also, use the object names for actual object names, not for keeping track of some properties
you can keep track of custom properties of any QObject using dynamic properties:
button.setProperty('cluster', cluster_number)
button.setProperty('pixel', pixel_number)
unless you have mandatory system requirements, you should really consider to switch to PyQt5, as PyQt4 is considered obsolete and deprecated/unsupported since 2015;
always prefer capitalized names for classes, as lower case names are normally used for variables and attributes only; read more on the
Style Guide for Python Code;
I have design a customized formlayout ui using pyqt5 and want to import variables back to the main function for further execution of the main function.
I have tried many ways to get the return values from the main function when the "OK" button has clicked but unable to get the variables from the main function.
Can you please guide me, how can i get the variables from the pyqt5 formlayout ui to main function -
Here is the Code of PyQt5 FormLayout UI function -
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout,QCheckBox)
import sys
app = QApplication([])
class Dialog(QDialog):
def __init__(self,dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.setWindowTitle("Form Layout")
def accept(self):
print(self.linedit1.text())
print(self.combox1.currentText())
print(self.spinbox1.value())
self.closeEvent()
def reject(self):
print('Cancelled')
self.closeEvent()
def getoutput(self):
return self.linedit1.text()
def createFormGroupBox(self,dinput):
self.formGroupBox = QGroupBox("Form layout")
layout = QFormLayout()
self.linedit1 = QLineEdit()
self.linedit1.setText('TestName')
layout.addRow(QLabel(dinput[0]), self.linedit1)
self.combox1 = QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
layout.addRow(QLabel(dinput[1]), self.combox1)
self.spinbox1 = QSpinBox()
layout.addRow(QLabel(dinput[2]), self.spinbox1)
self.formGroupBox.setLayout(layout)
Main Function is -
import os
import sys
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if(dialog.exec_()):
TName = dialog.getoutput
print('------------------')
print(TName)
# Main Function Continous by getting the inputs
# from UI
I am unable to get the desired values to the output function. Even i have used the getoutput function to return the values and get the output to "TName". But i am not able to get the value into the TName variable and nothing is displaying.
The Result i am getting is - (which is basically printing the accept button function but not the TName variable which is returned to Main function.
TestName
India
25
How can i get the return values from PyQt5 Formlayout UI function to Main function..?
In the first place, FormLayout is a layout, that is, a class that is responsible for positioning the widgets within a window, so it is irrelevant for these cases. On the other hand, closeEvent() should never be invoked, that is a function that serves to handle the closed window event.
Going to the point the accept method is called when Ok is pressed, so it is the right place to get the values so it must be stored in a variable, and then returned in the get_output() method:
pyformlayout.py
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
class Dialog(QtWidgets.QDialog):
def __init__(self, dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setWindowTitle("Form Layout")
def createFormGroupBox(self, dinput):
layout = QtWidgets.QFormLayout()
self.linedit1 = QtWidgets.QLineEdit('TestName')
self.combox1 = QtWidgets.QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
self.spinbox1 = QtWidgets.QSpinBox()
for text, w in zip(dinput, (self.linedit1, self.combox1, self.spinbox1)):
layout.addRow(text, w)
self.formGroupBox = QtWidgets.QGroupBox("Form layout")
self.formGroupBox.setLayout(layout)
def accept(self):
self._output = self.linedit1.text(), self.combox1.currentText(), self.spinbox1.value()
super(Dialog, self).accept()
def get_output(self):
return self._output
And in the file main.py I get the value if only the ok button has been pressed:
main.py
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if dialog.exec_() == pyfl.Dialog.Accepted:
name, item, value = dialog.get_output()
print(name, item, value)