Unsolved How can I connect Qcombobox with Qlineedit to import data from xml file ?
-
@jsulm could you give me example for it? I'm trying to solve it since 3 weeks but I still clueless
-
working on a fix for you @MomoAlex but one of your issues is that you are using the Designer to produce your code and the spews out some of the ugliest code I have ever seen -- I always strongly suggest that folks stop using it and learn to create that GUI from scratch it actually does not take that much longer and what you have when you are done is extremely easier to work with AND more importantly you have a better understanding of how to manipulate it to get it to do the things you want it to.
-
@Denni Thank you very much for your advice. I thought on the beginning that would be easier to use Qt Designer but at the end I lost a lot of time and as I said I still beginner and started with python for 2 months.
-
Okay using your program you have posted and using the data you have posted I get an error when it tries to parse your data can you please clean these up to make sure they are fully functional -- that will speed things up greatly
-
@Denni you can find them by this link:
https://drive.google.com/drive/folders/1fnSGc91_pam86NBCgZ12_d66MUw-5I6O?usp=sharing
thank you for your effort -
@Denni Do you have something new?
-
Should have something soon-ish -- keep in mind this is a side gig for me not my main one ;) However I do have most of it rewritten just need to get the final pieces in place and make sure all the items are connected
-
Okay I have restructured your program throwing out all that unnecessary garbage that the designer barfs up and replaced it with a much more streamlined pythonic classified flow -- I have also included explanation text where I thought might be helpful -- I have done this to help teach you how to fish rather than just giving you the fish -- there are elements to this that could be expanded upon and/or altered depending on your needs but this should give you a solid ground floor to work from as well as a template to do other things with -- again do yourself a major favor and stop using that Designer the code it gurgitates looks just like programming vomit and is very difficult to work with and doing it without the designer is actually just as easy or easier. So here is that code hope it helps:
import re from os import path as osPath from sys import exit as sysExit import xml.etree.ElementTree as etETree from PyQt5.QtCore import QSettings from PyQt5.QtWidgets import QApplication, QWidget, QTabWidget, QGridLayout, QDialogButtonBox, QVBoxLayout, QHBoxLayout from PyQt5.QtWidgets import QLabel, QLineEdit, QPushButton, QComboBox # This class is separate and as such could be removed and simply imported # it is done so that the MVC (Model-View-Controller) aspect of the program # can be maintained as well as to create an object that can be shared with # other programs without the need of rewriting or even copy/pasting class DataHandler: def __init__(self, SourcePath): self.Source = SourcePath # Establish Full Path to the Data Source # I have included this for completeness purposes mostly and because # it was mostly just a simple copy/paste for me to do so SourceFilePath = '' if len(SourcePath) > 0 and osPath.isfile(SourcePath): SourceFilePath = SourcePath else: SourceFilePath = str(QSettings('Company','Project').value('LastSource')) if len(SourceFilePath) == 0: SourceFilePath = self.SetDatabase() # If SourceFilePath is still empty the user has chosen not to supply a valid source abort the program if len(SourceFilePath) < 1: sysExit() QSettings('Company','Project').setValue('LastSource', SourceFilePath) self.__SourcePathName = SourceFilePath @property def Source(self): return self.__SourcePathName @Source.setter def Source(self, value): self.__SourcePathName = value # Again included for completeness purposes for this class and all that would be # needed is a button or file menu item to allow for choosing another file of course # some tweaking would be required since it was made to be totally generic # Lastly it was mostly just a simple copy/paste for me to do so def SetSource(self): filter = "ext(*.ext)" caption = "Please Select a Valid Source File" path = str(QSettings('Company','Project').value('LastSource')) if len(path) > 0: path = osPath.dirname(path) else: path = str(QDir.currentPath()) IsSource = False dbContinue = True while dbContinue: FindProj = QFileDialog() FindProj.setModal(True) FindProj.setFixedSize(FindProj.size()) filePathName = FindProj.getOpenFileName(None, caption, path, filter )[0]; # Is this a legitimate file if osPath.isfile(filePathName): filPthNam, fileExt = ntSplitext(filePathName) # Does it have a legitimate file extension if fileExt == '.ext': IsSource = True if not IsSource: ValidMsg = QMessageBox() ValidMsg.setIcon(QMessageBox.Warning) ValidMsg.setWindowTitle('Project Source File') ValidMsg.setText('This is not a Valid Source would you like to try again?') ValidMsg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) ValidMsg.setDefaultButton(QMessageBox.No) ValidMsg.setWindowFlags(Qt.WindowStaysOnTopHint) MsgReply = ValidMsg.exec_() if MsgReply == QMessageBox.No: dbContinue = False filePathName = '' else: QSettings('Company','Project').setValue("LastSource",osPath.dirname(filePathName)) dbContinue = False return filePathName self.__SQLDef = value def ReadSource(self): try: # Read Source file into local Dictionaries as working with raw data is never # nice and can have unexpected results at times best to catch it early also it # means the rest of the code can be handled the same way regardless of what the # source was and all that needs to be changed is this function if the source # changes later on xmlTree = etETree.parse(self.Source) root = xmlTree.getroot() # Note keep in mind that this has been engineered to handle a specific XML file # layout and what it returns if the files format changes then this would need to # either be changed or augmented in some way self.xmlData = {} for child in root: MatId = child.attrib['material_id'] Idx = 0 Nodes = {} for attrib in child: Tag = re.sub(r'.*}',' ',child[Idx].tag).strip() Txt = child[Idx].text Nodes[Tag] = Txt Idx += 1 self.xmlData[MatId] = Nodes.copy() except Exception as err: print("ERROR : ReadSource :",err) print("Source : [" + self.__SourcePathName + "]") sysExit() def GetCatalog(self): # The Catalog is simply a List of the Names found within our xmlData # Note might want to look into sorting this list alphabetically but # that might not be needed so did not do it Catalog = [] for item in self.xmlData: MatItem = self.xmlData[item] Catalog.append(MatItem['name']) return Catalog def GetMatItem(self, MatName): for item in self.xmlData: MatItem = self.xmlData[item] if MatItem['name'] == MatName: return MatItem return {} # We create a Tab Class (since they were all the same) so we can instantiate # more than one of these without having to reproduce the wheel each time class Tab(QWidget): def __init__(self, parent, inTabId): QTabWidget.__init__(self, parent) self.TabId = inTabId self.MainWin = parent # Spacer Label used to create space where desired # there might a more pyqt5 way of doing this though self.lblSpcr = QLabel(' ') ####### First GUI Section # Creating the Tab Box from Top Down to make it more Intuitive # Create the Load Catalog Button and give it an appropriate name # also keep associated things named in-sync for easier referencing self.btnCatLodr = QPushButton('Load Catalog') self.btnCatLodr.clicked.connect(self.LoadCatalog) # Note if the positioning of the button is desired to be more centered # then simply add another Stretch to the other side and play with the # Stretch values until you have it position more to your liking self.TopHHldr = QHBoxLayout() self.TopHHldr.addWidget(self.lblSpcr) self.TopHHldr.addWidget(self.btnCatLodr) self.TopHHldr.addStretch(1) ####### Second GUI Section # Next are the elements contained within the Grid as the other buttons do # not benefit from the Grid and actually are not as easy to implement since # the Stretch works differently within the Grid # Next was the Catalog ComboBox with its Label gave it a more appropriate # name but also was not sure if its current label had been misplaced or not self.lblCatg = QLabel('Catalog') self.cboCatg = QComboBox() self.cboCatg.activated['QString'].connect(self.MatChanged) self.cboCatg.setObjectName("cboCat") self.cboCatg.addItem('') self.cboCatg.setItemText(0, 'Plese Press Load Catalog') # Material and its Label # Note this is redundant since it gets displayed as the selection # for the combobox but you had it here so I included it self.lblMatl = QLabel('Material') self.ledMatl = QLineEdit() self.ledMatl.setPlaceholderText('Material Name') # Area and its Label self.lblArea = QLabel('Area') self.ledArea = QLineEdit() self.ledArea.setPlaceholderText('m²') # Thickness and its Label self.lblThck = QLabel('Thickness') self.ledThck = QLineEdit() self.ledThck.setPlaceholderText('m') # U-Value and its Label self.lblUVal = QLabel('U-Value') self.ledUVal = QLineEdit() self.ledUVal.setPlaceholderText('W/(K*m²)') # Density and its Label self.lblDens = QLabel('Density') self.ledDens = QLineEdit() self.ledDens.setPlaceholderText('kg/m³') # Conductivity and its Label self.lblCond = QLabel('Conductivity') self.ledCond = QLineEdit() self.ledCond.setPlaceholderText('W/(K*m)') # Heat Capacity and its Label self.lblHCap = QLabel('Heat Capacity') self.ledHCap = QLineEdit() self.ledHCap.setPlaceholderText('J/K') # Now we put all of these into our Grid # Note although not necessary, the reason for uniform abbreviation # lengths are to assist in easier readability as can be seen below self.TheGrid = QGridLayout() self.TheGrid.setObjectName("TheGrid") self.TheGrid.addWidget(self.lblSpcr, 0, 0) self.TheGrid.addWidget(self.lblCatg, 1, 0) self.TheGrid.addWidget(self.cboCatg, 1, 1) self.TheGrid.addWidget(self.lblSpcr, 2, 0) self.TheGrid.addWidget(self.lblMatl, 3, 0) self.TheGrid.addWidget(self.ledMatl, 3, 1) self.TheGrid.addWidget(self.lblSpcr, 4, 0) self.TheGrid.addWidget(self.lblArea, 5, 0) self.TheGrid.addWidget(self.ledArea, 5, 1) self.TheGrid.addWidget(self.lblSpcr, 6, 0) self.TheGrid.addWidget(self.lblThck, 7, 0) self.TheGrid.addWidget(self.ledThck, 7, 1) self.TheGrid.addWidget(self.lblSpcr, 8, 0) self.TheGrid.addWidget(self.lblUVal, 9, 0) self.TheGrid.addWidget(self.ledUVal, 9, 1) self.TheGrid.addWidget(self.lblSpcr,10, 0) self.TheGrid.addWidget(self.lblDens,11, 0) self.TheGrid.addWidget(self.ledDens,11, 1) self.TheGrid.addWidget(self.lblSpcr,12, 0) self.TheGrid.addWidget(self.lblCond,13, 0) self.TheGrid.addWidget(self.ledCond,13, 1) self.TheGrid.addWidget(self.lblSpcr,14, 0) self.TheGrid.addWidget(self.lblHCap,15, 0) self.TheGrid.addWidget(self.ledHCap,15, 1) ####### Third GUI Section # Next we make the buttons at the bottom of the screen # in such a way that we can use them later on more efficiently # # Note the cleaner implementation of these 2 buttons would have them # be outside the Tabs but since you included them inside then so have I self.btnOkay = QPushButton('Okay') self.btnOkay.setObjectName('btnOkay') self.btnOkay.clicked.connect(self.OkayPressed) self.btnCancel = QPushButton('Cancel') self.btnCancel.setObjectName('btnCancel') self.btnCancel.clicked.connect(self.CancelPressed) self.BtmHHldr = QHBoxLayout() self.BtmHHldr.addStretch(2) self.BtmHHldr.addWidget(self.btnOkay) self.BtmHHldr.addWidget(self.btnCancel) ####### Final GUI Section # Now we put our 3 major pieces into a Vertial Container self.VLayout = QVBoxLayout() self.VLayout.addWidget(self.lblSpcr) self.VLayout.addLayout(self.TopHHldr) self.VLayout.addLayout(self.TheGrid) self.VLayout.addStretch(1) self.VLayout.addLayout(self.BtmHHldr) # And we put that vertical container within our Widget self.setLayout(self.VLayout) def LoadCatalog(self): # This is implemented as such because parameters cannot # be included within a connect statement and it allows for # easier autonomy self.MainWin.LoadCatalog(self.TabId) def MatChanged(self): # This is implemented as such because parameters cannot # be included within a connect statement and it allows for # easier autonomy self.MainWin.SelectnChngd(self.TabId) def OkayPressed(self): # Place Holder Function for the most part print('You pressed Okay') def CancelPressed(self): # Place Holder Function for the most part print('You pressed Cancel') sysExit() class MainTabWidget(QTabWidget): def __init__(self, SourceFile, parent=None): QTabWidget.__init__(self, parent) self.setObjectName("TabWidgetWalls") self.resize(494, 493) self.setTabShape(QTabWidget.Triangular) self.Source = DataHandler(SourceFile) self.SourceLoaded = False self.Tab = [] self.Tab.append(Tab(self, 0)) self.addTab(self.Tab[0], "Wall 1") self.Tab.append(Tab(self, 1)) self.addTab(self.Tab[1], "Wall 2") self.Tab.append(Tab(self, 2)) self.addTab(self.Tab[2], "Wall 3") self.Tab.append(Tab(self, 3)) self.addTab(self.Tab[3], "Wall 4") self.setCurrentIndex(0) # These are located out here because they have less to do with the # GUI View and more to do with communicating with the Model and directing # the data to the GUI View -- aka this is sort-of the Controller def LoadCatalog(self, TabIdx): if not self.SourceLoaded: # Do Not want to do this more than once per file # If a load new file is added then SourceLoaded # would be set to False within that routine however # if this is needed separately for each Tab then it # needs to be an array instead of just a value self.SourceLoaded = True self.Source.ReadSource() self.Catalog = self.Source.GetCatalog() self.Tab[TabIdx].cboCatg.clear() self.Tab[TabIdx].cboCatg.addItem('Make Selection') for MatName in self.Catalog: self.Tab[TabIdx].cboCatg.addItem(MatName) def SelectnChngd(self, TabIdx): MatName = self.Tab[TabIdx].cboCatg.currentText() self.MatItem = self.Source.GetMatItem(MatName) if 'name' in self.MatItem: self.Tab[TabIdx].ledMatl.setText(self.MatItem['name']) if 'area' in self.MatItem: self.Tab[TabIdx].ledArea.setText(self.MatItem['area']) if 'thickness' in self.MatItem: self.Tab[TabIdx].ledThck.setText(self.MatItem['thickness']) if 'u-value' in self.MatItem: self.Tab[TabIdx].ledUVal.setText(self.MatItem['u-value']) if 'density' in self.MatItem: self.Tab[TabIdx].ledDens.setText(self.MatItem['density']) if 'thermal_conduc' in self.MatItem: self.Tab[TabIdx].ledCond.setText(self.MatItem['thermal_conduc']) if 'heat_capac' in self.MatItem: self.Tab[TabIdx].ledHCap.setText(self.MatItem['heat_capac']) if __name__ == "__main__": MainThred = QApplication([]) # I moved this here to simulate using sys.argv to receive # a command line file name however this could just as easily # be excluded and the user could be prompted using the class # method to give the program its source file or it could be # hard-coded into that class but then hard-coding anything # (like the below) is generally a bad idea base_path = osPath.dirname(osPath.realpath(__file__)) SourceFile = osPath.join(base_path, "Data\\MaterialTemplates_v4.xml") MainGUI = MainTabWidget(SourceFile) MainGUI.show() sysExit(MainThred.exec_())
P.S. Please let me know if this has helped anyone -- I do not need kudos but knowing that it actually helped someone is encouraging
-
@Denni
that is really amazing and that helped me a lot. Thank you very much for making it easier for me and I would like to stay with you in contact if you don't mind.
How could I improve my skills in Qt? -
Feel free to reach out anytime djensen765@gmail.com
How to improve your skills? Just keep working at it and do your best not to settle for second best. Put quality on the top of your list then just practice and think about what you are doing. Sometimes re-think about what you were doing and see if you might not have done it better. Do NOT adhere to that adage that "If it aint broke then dont fix it" because that thinking is what causes lots of issues later on -- like having the design of the worlds most sophisticated mode of travel being dictated to by the width of 2 horses rear-ends because sometimes just because it works does not mean it works well or is the best. Yes there is the need for the 80% solution but if you strive for the stars you just might end up on the moon but if you aim for a mud puddle you will definitely be knee deep in it.
Lastly engineer your program to be as much Model-Controller-View as you can keeping each element as separate and self contained as possible this allows for easier plug-n-play of components later on allowing you to re-use perhaps whole classes of code for numerous projects. Also if you find yourself creating duplicate or even semi-similar code sections perhaps it ought to be made more generic and done once instead of twice (or more). Oh and help others sometimes the greatest learning comes from trying to teach someone else -- as the greatest teachers in the world are ones that constantly learn from their students.