Dynamic adding of textbox in pyqt5
-
hi, guys,
i have a question about a gui project. If the user selects one or more images on the gui, the names of the images should be displayed in text boxes. Currently i have implemented 3 textboxes statically. Therefore only the names of 3 images are displayed. Is there a way to make this more dynamic ? For example, if the user selects more than 3, the gui will be extended by two more textboxes ?
I build the gui with qt designer. In the image, you can see the prototype gui
. And this is the code:
def __init__(self): super(Fenster, self).__init__() loadUi(r'C:\Users\NAME\Desktop\xyz\Code_Fr_Forum\test_.ui', self) self.pushButton.clicked.connect(self.load_img) def load_img(self): filename, _ = QFileDialog.getOpenFileNames(self, 'Select Multi File', 'default', 'All Files (*)') count = len(filename) #Myidea: Depend on the number of filenames, add more than 3 lineEdit on #the GUI (dynamic). #))Now its very static for index, name in enumerate(filename, start=1): # extract file name basename = os.path.basename(name) filename_list.append(basename) if index == 1: self.lineEdit.setText(basename) elif index == 2 self.lineEdit_1.setText(basename) elif index == 3 and idx != -1: self.lineEdit_2.setText(basename) #elif index > 3: #add self.lineEdit_3 under self.lineEdit_2 self.lineEdit_3.setText(basename) ######MAIN#################### app = QApplication(sys.argv) w = Fenster() w.show() sys.exit(app.exec_())```
-
@elias_hh said in Dynamic adding of textbox in pyqt5:
Is there a way to make this more dynamic ?
Of course. You can add a list member variable where you store your line edits.
for index, name in enumerate(filename, start=1): # extract file name basename = os.path.basename(name) filename_list.append(basename) self.lineEdits += QLineEdit(self) # Make sure to set correct parent # Use the line edit you just added
Use a layout to organise all this widgets.
-
@elias_hh here is a MUC that shows you how to do many things -- as well as explains why for somethings as well -- if you have any questions on it just ask.
from PyQt5.QtCore import pyqtSlot from PyQt5.QtGui import QFont, QStandardItemModel, QStandardItem from PyQt5.QtWidgets import QApplication, QStyleFactory, QMainWindow, QWidget from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QPushButton from PyQt5.QtWidgets import QFileDialog, QLineEdit, QTextEdit from PyQt5.QtWidgets import QListView from os import path as osPath class OutWithListView(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.lvwImgLst = QListView() self.lvwModel = QStandardItemModel() self.lvwImgLst.setModel(self.lvwModel) # These two are not really need just included to make # all the classes interchangeable self.PrimrSet = False self.LstIndx = 0 VBox = QVBoxLayout() VBox.addWidget(self.lvwImgLst) self.setLayout(VBox) # Note Indx is used here only to make the various classes # interchangeable it is not needed otherwise def SetImage(self, Text, Indx): # One could add Images to this one such as the file icon # Item = QStandardItem(QIcon(SomeIcon), Text) Item = QStandardItem(Text) self.lvwModel.appendRow(Item) class OutWithTextEdit(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.txeImgLst = QTextEdit() # These two are not really need just included to make # all the classes interchangeable self.PrimrSet = False self.LstIndx = 0 VBox = QVBoxLayout() VBox.addWidget(self.txeImgLst) self.setLayout(VBox) # Note Indx is used here only to make the various classes # interchangeable it is not needed otherwise def SetImage(self, Text, Indx): self.txeImgLst.append(Text) # Note this would most likely need a means to either re-size # the window or better yet have a Scroll Area added to it class OutWithLabels(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.LblViews = {} self.LblViews[0] = QLabel() self.PrimrSet = False self.LstIndx = 0 self.EditBox = QVBoxLayout() self.EditBox.addWidget(self.LblViews[0]) self.setLayout(self.EditBox) def SetImage(self, Text, Indx): if Indx not in self.LblViews.keys(): self.LblViews[Indx] = QLabel() self.EditBox.addWidget(self.LblViews[Indx]) self.LblViews[Indx].setText(Text) # Note this would most likely need a means to either re-size # the window or better yet have a Scroll Area added to it class OutWithLineEdits(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.LneEdts = {} self.LneEdts[0] = QLineEdit() self.PrimrSet = False self.LstIndx = 0 self.EditBox = QVBoxLayout() self.EditBox.addWidget(self.LneEdts[0]) self.setLayout(self.EditBox) def SetImage(self, Text, Indx): print('Setting Image ',Indx,')',Text) if Indx not in self.LneEdts.keys(): self.LneEdts[Indx] = QLineEdit() self.EditBox.addWidget(self.LneEdts[Indx]) self.LneEdts[Indx].setText(Text) # So here is how to make that (or any) GUI using straight Python-Qt class LoadUI(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.FileNameLst = [] self.btnOpnImg = QPushButton('Open Image') self.btnOpnImg.clicked.connect(self.LoadImage) HBox1 = QHBoxLayout() HBox1.addWidget(self.btnOpnImg) HBox1.addStretch(1) Font = QFont() Font.setPointSize(16) Font.setBold(True) lblSelectd = QLabel('Your Selected Images:') lblSelectd.setFont(Font) HBox2 = QHBoxLayout() HBox2.addWidget(lblSelectd) HBox2.addStretch(1) # Comment-Out what you do not want to use and Uncomment-Out what you do # they have been made to be interchangable so you can see what each one # would look like # Now displaying the list of selected images could actually be done # several ways # 1) Your method of using QLineEdits self.OutPut = OutWithLineEdits(self) # 2) Using QLabels instead # self.OutPut = OutWithLabels(self) # 3) Using a QTextEdit instead # self.OutPut = OutWithTextEdit(self) # 4) Using a QListView instead # self.OutPut = OutWithListView(self) VBox = QVBoxLayout() VBox.addLayout(HBox1) VBox.addLayout(HBox2) VBox.addWidget(self.OutPut) VBox.addStretch(1) self.setLayout(VBox) # This is just a simple redirect or pass through method used for # ease of reading as well as implementation as modifying is very # easy later on @pyqtSlot() def LoadImage(self): FileData, NotUsed = QFileDialog.getOpenFileNames(self, 'Select Multi File', 'default', 'All Files (*)') if len(FileData) > 0: # Enumeration was unnecessary, especially since it was not used for FileItem in FileData: # Extract the File Name BaseName = osPath.basename(FileItem) self.FileNameLst.append(BaseName) if not self.OutPut.PrimrSet: self.OutPut.PrimrSet = True Indx = 0 else: self.OutPut.LstIndx += 1 Indx = self.OutPut.LstIndx self.OutPut.SetImage(BaseName, Indx) # Keep your Main Window simple let it handle those things that are outside your # actual Graphic User Interface such MenuToolBar, StatusBar, and Dockable Windows # if you use any of these class Fenster(QMainWindow): def __init__(self): # One should not use super( ) in Python as it introduces 4 known issues that # then must be handled properly. Further there were still actual bugs within # the usage of super( ) when used in Python as of early this year. So yes # while super( ) works fine within C++ it does not work as seamlessly within # Python due to the major differences between these two languages. Next the # reason it was created was to handle a rather rare issue and unless you are # doing some complicated inheritance you will most likely never run into this # extremely rare issue. However the 4 major issues that get included by using # super( ) are much more likely to occur than that rare issue its meant for to # solve. Of course using the basic explicit method, as follows, does not cause # these issues and is as just as simple as using `super( )` further you do not # actually gain anything useful by using `super( )` in Python that could not be # done in a much safer manner. QMainWindow.__init__(self) self.setWindowTitle('Image Selector') WinLeft = 550; WinTop = 150; WinWidth = 350; WinHigh = 300 self.setGeometry(WinLeft, WinTop, WinWidth, WinHigh) # The Center Pane is your actual Main Graphic User Interface self.CenterPane = LoadUI(self) self.setCentralWidget(self.CenterPane) self.StatBar = self.statusBar() def DsplyStatMsg(self, MsgTxt): self.StatBar.showMessage(MsgTxt) ######MAIN#################### if __name__ == "__main__": # Unless you plan to do something with Command Line arguments no need # to include this and if you are you are better off using argparse # library as it handles Command Line arguments much more cleanly # app = QApplication(sys.argv) MainEventThred = QApplication([]) MainApplication = Fenster() MainApplication.show() # This is the Qt4 method of doing this # sys.exit(app.exec_()) # While this is the Qt5 method for doing this MainEventThred.exec()
-
@Denni-0 Hey, I thank you for the very detailed and helpful MUC. My later GUI looks much more complex than in the photo, which I showed as an example. Therefore I create the GUI with QT Designer (static). Is there still the possibility to change the GUI I created with QT Designer dynamically and add some textboxes to my main program ?
With your MUC it is possible, but is it also possible to do this with a GUI that was created with Designer ?
-
@jsulm thank u very much. Would this also work if my GUI was created with QT Designer ? So I can overwrite the GUI with
self.lineEdits += QLineEdit(self)
afterwards with several textbox's ?
-
@elias_hh I think if you want to modify your UI at runtime you will need to change your approach: do not use loadUi but instead use pyuic to generate Python code from ui file (which is XML).
Take a look at https://doc.qt.io/qtforpython/tutorials/basictutorial/uifiles.html
-
@elias_hh
I would recommend doing as @jsulm says. It is then easy to mix design-time & run-time widgets as desired, if that's what you want to do.
-
Hey, guys,
Thanks for your help. I have decided to completely rethink my approach and work without Qt Designer. I will use the code from @Denni-0 as a base because it is almost exactly the way I want it to be. My gui should look like the following picture (designed with qt designer):
My idea is that the program filters the required parameters from the names of the images. I know how to do that and it works. But as you can see in the picture I need two more QLineEdits(). One for Heights and one for Width. These act as security to see if the images are named correctly. If the user has named the images according to the correct nomenclature, the parameters appear there. If the image names were named incorrectly, a question mark appears there.
But my main problem now is, I don't know how to position these textboxes next to the image name. I can only do it below. I tried with xyz.move(x,y) but the textboxes are still below. How can i solve this ?
-
@elias_hh said in Dynamic adding of textbox in pyqt5:
how to position these textboxes next to the image name
You need to put them into the correct layout, don't use move().
Please show your code and where you want to add text boxes.
-
@jsulm i threw out my code and want to use @Denni-0 . The only difference is that I want to have two more textboxes next to the textbox with the name (see figure).
-
@elias_hh okay so how to render that Designer View in Python-Qt well here is a version of the above in a Layout form only hope this helps you moving forward
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QStyleFactory from PyQt5.QtWidgets import QGridLayout, QLabel from PyQt5.QtWidgets import QPushButton, QLineEdit, QTextEdit class BigButton(QPushButton): def __init__(self, text): QPushButton.__init__(self) Font = QFont() Font.setPointSize(12) Font.setBold(True) Hight=50; Width=150 self.setFixedSize(Width, Hight) self.setFont(Font) self.setText(text) # Note considering what you are doing with this I would # strongly suggest you replace the following with a QTreeView # with multiple columns as that would handle it much cleaner # and efficiently but here is literally what you presented class SelectdImgs(QWidget): def __init__(self, parent): QWidget.__init__(self) Font = QFont() Font.setBold(True) lblSelectd = QLabel('Selected Images') lblSelectd.setFont(Font) lblHight = QLabel('Height') lblHight.setFont(Font) lblWidth = QLabel('Width') lblWidth.setFont(Font) HiWi = 40 # These could have been sub-classed like the buttons but # thought I would show both methods and why sub-classing # style elements can be very useful when they are repeated lneImgDesc1 = QLineEdit('Rectangle_abc_Height:5m_Width:8m') # Due to how the layout works, setting this minimum value # for this one sets it for all of them lneImgDesc1.setMinimumWidth(200) lneImgHiht1 = QLineEdit('5') lneImgHiht1.setMaximumWidth(HiWi) lneImgHiht1.setAlignment(Qt.AlignHCenter) lneImgWdth1 = QLineEdit('8') lneImgWdth1.setMaximumWidth(HiWi) lneImgWdth1.setAlignment(Qt.AlignHCenter) lneImgDesc2 = QLineEdit('Rectangle_def_Height:8m_Width:8m') lneImgHiht2 = QLineEdit('8') lneImgHiht2.setMaximumWidth(HiWi) lneImgHiht2.setAlignment(Qt.AlignHCenter) lneImgWdth2 = QLineEdit('8') lneImgWdth2.setMaximumWidth(HiWi) lneImgWdth2.setAlignment(Qt.AlignHCenter) lneImgDesc3 = QLineEdit('Rectangle_ghi_Height:2m_Width:2m') lneImgHiht3 = QLineEdit('2') lneImgHiht3.setMaximumWidth(HiWi) lneImgHiht3.setAlignment(Qt.AlignHCenter) lneImgWdth3 = QLineEdit('2') lneImgWdth3.setMaximumWidth(HiWi) lneImgWdth3.setAlignment(Qt.AlignHCenter) lneImgDesc4 = QLineEdit('Rectangle_jkl_Height:5m_Width:4m') lneImgHiht4 = QLineEdit('5') lneImgHiht4.setMaximumWidth(HiWi) lneImgHiht4.setAlignment(Qt.AlignHCenter) lneImgWdth4 = QLineEdit('4') lneImgWdth4.setMaximumWidth(HiWi) lneImgWdth4.setAlignment(Qt.AlignHCenter) grdView = QGridLayout() grdView.addWidget(lblSelectd,0,0) grdView.addWidget(lblHight,0,1) grdView.addWidget(lblWidth,0,2) grdView.addWidget(lneImgDesc1,1,0) grdView.addWidget(lneImgHiht1,1,1) grdView.addWidget(lneImgWdth1,1,2) grdView.addWidget(lneImgDesc2,2,0) grdView.addWidget(lneImgHiht2,2,1) grdView.addWidget(lneImgWdth2,2,2) grdView.addWidget(lneImgDesc3,3,0) grdView.addWidget(lneImgHiht3,3,1) grdView.addWidget(lneImgWdth3,3,2) grdView.addWidget(lneImgDesc4,4,0) grdView.addWidget(lneImgHiht4,4,1) grdView.addWidget(lneImgWdth4,4,2) grdView.setColumnStretch(0, 1) self.setLayout(grdView) class CenterPanel(QWidget): def __init__(self, parent): QWidget.__init__(self) self.Parent = parent self.btnSelect = BigButton('Select') self.btnSelect.clicked.connect(self.Selectd) self.ImgView = SelectdImgs(self) self.btnCalc = BigButton('Start Calc') self.btnCalc.clicked.connect(self.CalcIt) self.txeOutput = QTextEdit() self.txeOutput.append('Rectangle_abc = 40 m²') self.txeOutput.append('Rectangle_def = 64 m²') self.txeOutput.append('Rectangle_ghi = 4 m²') self.txeOutput.append('Rectangle_jkl = 20 m²') VBox = QVBoxLayout() VBox.addWidget(self.btnSelect) VBox.addWidget(self.ImgView) VBox.addWidget(self.btnCalc) VBox.addWidget(self.txeOutput) self.setLayout(VBox) @pyqtSlot() def Selectd(self): self.Parent.SetStatusMsg('Item Selected') @pyqtSlot() def CalcIt(self): self.Parent.SetStatusMsg('Calculating Items') class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setWindowTitle('Main Window') WinLeft = 150; WinTop = 150; WinWidth = 500; WinHigh = 500 self.setGeometry(WinLeft, WinTop, WinWidth, WinHigh) self.CenterPane = CenterPanel(self) self.setCentralWidget(self.CenterPane) self.StatBar = self.statusBar() self.setStyle(QStyleFactory.create('Cleanlooks')) def SetStatusMsg(self, StatusMsg=''): if len(StatusMsg) < 1: StatusMsg = 'Ready' self.StatBar.showMessage(StatusMsg) if __name__ == "__main__": MainEventThred = QApplication([]) MainApplication = MainWindow() MainApplication.show() MainEventThred.exec()