简体   繁体   English

PyQt - 将 XML 加载到 QTreeWidget

[英]PyQt - load XML into QTreeWidget

I'm trying to make a basic XML story maker in PyQt.我正在尝试在 PyQt 中制作一个基本的 XML 故事制作器。 So far I've been able to figure out everything my self, but I've run into a bit of a snag.到目前为止,我已经能够自己弄清楚一切,但我遇到了一些障碍。 I can't figure out how to load an XML file into a QTreeWidget.我不知道如何将 XML 文件加载到 QTreeWidget 中。 I need to preserve the hierarchy, but said hierarchy is seemingly random.我需要保留层次结构,但所说的层次结构似乎是随机的。 I'll post my code, just keep in mind the Open function doesn't work (for obvious reasons):我将发布我的代码,请记住 Open 函数不起作用(出于明显的原因):

import sys, os
from PyQt4 import Qt, QtGui, QtCore

class MainWindow( QtGui.QMainWindow ):
    width = 640
    height = 480
    
    def __init__( self ):       
        QtGui.QMainWindow.__init__( self )
        
        self.setWindowTitle( "Story Maker" )
        self.setWindowIcon( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/text-x-generic.png' ) )
        
        self.fname = None
        
        menubar = self.menuBar()
        
        newAction = QtGui.QAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/document-new.png' ), '&New', self )
        newAction.setShortcut( 'Ctrl+N' )
        newAction.setStatusTip( 'Start new story' )
        
        saveAction = QtGui.QAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/document-save.png' ), '&Save', self )
        saveAction.setShortcut( 'Ctrl+S' )
        saveAction.setStatusTip( 'Save story' )
        
        saveAsAction = QtGui.QAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/document-save-as.png' ), '&Save as...', self )
        saveAsAction.setShortcut( 'Ctrl+Shift+S' )
        saveAsAction.setStatusTip( 'Save story as...' )
        
        openAction = QtGui.QAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/document-open.png' ), '&Open', self )
        openAction.setShortcut( 'Ctrl+O' )
        openAction.setStatusTip( 'Open story' )
        
        exitAction = QtGui.QAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/process-stop.png' ), '&Exit', self )
        exitAction.setShortcut( 'Ctrl+Q' )
        exitAction.setStatusTip( 'Exit application' )
        
        fileMenu = menubar.addMenu( '&File' )
        fileMenu.addAction( newAction )
        fileMenu.addAction( saveAction )
        fileMenu.addAction( saveAsAction )
        fileMenu.addAction( openAction )
        fileMenu.addSeparator();
        fileMenu.addAction( exitAction )
        
        main = QtGui.QWidget()
        
        grid = QtGui.QGridLayout()
        
        self.tree = QtGui.QTreeWidget( self )
        self.tree.setHeaderLabels( [ 'Text', 'ID' ] )
        self.tree.setContextMenuPolicy( QtCore.Qt.CustomContextMenu )
        self.tree.customContextMenuRequested.connect( self.contextMenu )
        item = QtGui.QTreeWidgetItem( [ 'Main' ], -1 )
        self.tree.addTopLevelItem( item )
        self.tree.setItemSelected( item, True )
        
        self.addText = QtGui.QPushButton( 'Add Text', self )
        addOption = QtGui.QPushButton( 'Add Option', self )
        self.remove = QtGui.QPushButton( 'Remove', self )
        
        grid.addWidget( self.tree, 0, 0, 1, 3 )
        grid.addWidget( self.addText, 1, 0 )
        grid.addWidget( addOption, 1, 1 )
        grid.addWidget( self.remove, 1, 2 )
        
        main.setLayout( grid )
        
        self.setCentralWidget( main )
        
        self.resize( MainWindow.width, MainWindow.height )
        self.center()
        
        self.statusBar().showMessage( 'Ready' )
        
        newAction.triggered.connect( self.newClicked )
        saveAction.triggered.connect( self.saveClicked )
        saveAsAction.triggered.connect( self.saveAsClicked )
        openAction.triggered.connect( self.openClicked )
        exitAction.triggered.connect( QtGui.qApp.quit )
        
        self.connect( self.addText, QtCore.SIGNAL( 'clicked()' ), self.addTextClicked )
        self.connect( addOption, QtCore.SIGNAL( 'clicked()' ), self.addOptionClicked )
        self.connect( self.remove, QtCore.SIGNAL( 'clicked()' ), self.removeClicked )
        
        self.tree.currentItemChanged.connect( self.onChanged )
    
    def contextMenu( self, position ):
        item = self.tree.selectedItems()[0]
        
        menu = QtGui.QMenu()
        if( self.addText.isEnabled() ):
            menu.addAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/list-add.png' ), self.tr( "Add Text" ) )
        menu.addAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/list-add.png' ), self.tr( "Add Option" ) )
        if( item.type() != -1 ):
            menu.addAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/list-remove.png' ), self.tr( "Delete" ) )
        if( item.isExpanded() ):
            menu.addAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/go-up.png' ), self.tr( "Collapse" ) )
        else:
            menu.addAction( QtGui.QIcon( os.path.dirname( os.path.realpath( __file__ ) ) + '/res/icons/go-down.png' ), self.tr( "Expand" ) )
        option = menu.exec_( self.tree.viewport().mapToGlobal( position ) )
        
        if( option != None ):
            main = self.tree.topLevelItem( 0 )
            
            if( option.text() == "Add Text" ):
                new = QtGui.QTreeWidgetItem( [ 'Text', 'DON\'T EDIT' ], 0 )
                new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
                
                item.addChild( new )
                item.setExpanded( True )
            if( option.text() == "Add Option" ):
                new = QtGui.QTreeWidgetItem( [ 'Text', '0' ], 1 )
                new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
                
                item.addChild( new )
                item.setExpanded( True )
            elif( option.text() == "Delete" ):
                main.removeChild( item )
            elif( option.text() == "Collapse" ):
                item.setExpanded( False )
            elif( option.text() == "Expand" ):
                item.setExpanded( True )

    def newClicked( self ):
        answer = QtGui.QMessageBox.question( self, 'Confirm', 'All unsaved data will be lost, continue?', QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No )
        
        if( answer == QtGui.QMessageBox.Yes ):
            self.tree.clear()
            item = QtGui.QTreeWidgetItem( [ 'Main' ], -1 )
            self.tree.addTopLevelItem( item )
            self.tree.setItemSelected( item, True )

    def save( self ):
        f = open( self.fname, 'w' )
            
        main = self.tree.topLevelItem( 0 )
        
        f.write( "<main>\n" )
                
        self.recursiveSave( main, f )
        
        f.write( "</main>\n" )
        
        f.close()

    def recursiveSave( self, main, f ):
        if( main.childCount() > 0 ):
            for i in xrange( main.childCount() ):
                item = main.child( i )
                
                t = item.type()
                
                if( t == 0 ):
                    f.write( "<text text='" + item.text( 0 ) + "'>\n" )
                elif( t == 1 ):
                    f.write( "<option id='" + item.text( 1 ) + "' text='" + item.text( 0 ) + "'>\n" )
                
                self.recursiveSave( item, f )
                
                if( t == 0 ):
                    f.write( "</text>\n" )
                if( t == 1 ):
                    f.write( "</option>\n" )

    def saveClicked( self ):
        if( self.fname == None ):
            self.saveAsClicked()
        else:
            self.save()
        
    def saveAsClicked( self ):
        dialog = QtGui.QFileDialog( self )
        dialog.setAcceptMode( QtGui.QFileDialog.AcceptSave )
        dialog.setFileMode( QtGui.QFileDialog.AnyFile )
        dialog.setFilter( self.tr( 'Stories (*.story)' ) )
        dialog.setDefaultSuffix( self.tr( 'story' ) )
        dialog.setConfirmOverwrite( True )
        dialog.exec_()
        
        fname = dialog.selectedFiles()[0]
        
        if( fname != None and fname != '' ):
            if( not fname.endsWith( '.story' ) ):
                dot = fname.lastIndexOf( '.' )
                
                if( dot > -1 ):
                    fname = fname[0:dot]
                    
                fname += '.story'
            
            self.fname = fname
            
            self.save()
        
    def openClicked( self ):
        fname = QtGui.QFileDialog.getOpenFileName( self, 'Open file...', '', 'Stories (*.story)' )
        
        if( fname != None and fname != '' ):
            self.fname = fname
            
            f = QtCore.QFile( fname )
            f.open( QtCore.QIODevice.ReadOnly )
            
            xml = QtCore.QXmlStreamReader( f )
            
            parent = self.tree.topLevelItem( 0 )
            previousParent = None
            
            while( xml.atEnd() != True ):
                xml.readNext()
                
                if( xml.isStartDocument() ):
                    continue
                
                if( xml.isStartElement() ):
                    if( xml.name() == "option" ):
                        print( "<option id='" + str( xml.attributes().value( 'id' ) ) + "' text='" + str( xml.attributes().value( 'text' ) ) + "'>" )
                        
                        new = QtGui.QTreeWidgetItem( [ str( xml.attributes().value( 'text' ) ), str( xml.attributes().value( 'id' ) ) ], 1 )
                        new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
                        
                        parent.addChild( new )
                        parent.setExpanded( True )
                        
                        previousParent = parent
                        parent = new
                    if( xml.name() == "text" ):
                        print( "<text text='" + str( xml.attributes().value( 'text' ) ) + "'>" )
                        
                        new = QtGui.QTreeWidgetItem( [ str( xml.attributes().value( 'text' ) ), 'DON\'T EDIT' ], 0 )
                        new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
                        
                        parent.addChild( new )
                        parent.setExpanded( True )
                        
                        previousParent = parent
                        parent = new
                elif( xml.isEndElement() ):
                    if( xml.name() == "option" ):
                        print( "</option>" )
                    elif( xml.name() == "text" ):
                        print( "</text>" )
                    
                    parent = previousParent
            
            xml.clear()
            f.close()
            
    def addTextClicked( self ):
        item = self.tree.selectedItems()[0]
        
        new = QtGui.QTreeWidgetItem( [ 'Text', 'DON\'T EDIT' ], 0 )
        new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
        
        item.addChild( new )
        item.setExpanded( True )
        
    def addOptionClicked( self ):
        item = self.tree.selectedItems()[0]
        
        new = QtGui.QTreeWidgetItem( [ 'Text', '0' ], 1 )
        new.setFlags( new.flags() | ( QtCore.Qt.ItemIsEditable ) )
        
        item.addChild( new )
        item.setExpanded( True )
        
    def removeClicked( self ):
        item = self.tree.selectedItems()[0]
        main = self.tree.topLevelItem( 0 )
        main.removeChild( item )
        
    def onChanged( self, current, previous ):
        if( current != None ):
            if( current.type() == -1 ):
                self.remove.setEnabled( False )
            else:
                self.remove.setEnabled( True )
            
            if( current.type() == 0 ):
                self.addText.setEnabled( False )
            else:
                self.addText.setEnabled( True )
    
    def center( self ):
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()

        widthDif = screen.width() - size.width()
        heightDif = screen.height() - size.height()
        
        x = widthDif / 2
        y = heightDif / 2
        
        self.move( x, y )

def main():
    app = QtGui.QApplication( sys.argv );
    
    window = MainWindow();
    window.show();
    
    sys.exit( app.exec_() ) 

if __name__ == '__main__':
    main()

It looks like you you could use QXmlDefaultHandler to do this.看起来您可以使用QXmlDefaultHandler来执行此操作。

Here's a simple demo script that shows how to use it:这是一个简单的演示脚本,展示了如何使用它:

from PyQt4 import QtCore, QtGui, QtXml

class XmlHandler(QtXml.QXmlDefaultHandler):
    def __init__(self, root):
        QtXml.QXmlDefaultHandler.__init__(self)
        self._root = root
        self._item = None
        self._text = ''
        self._error = ''

    def startElement(self, namespace, name, qname, attributes):
        if qname == 'folder' or qname == 'item':
            if self._item is not None:
                self._item = QtGui.QTreeWidgetItem(self._item)
            else:
                self._item = QtGui.QTreeWidgetItem(self._root)
            self._item.setData(0, QtCore.Qt.UserRole, qname)
            self._item.setText(0, 'Unknown Title')
            if qname == 'folder':
                self._item.setExpanded(True)
            elif qname == 'item':
                self._item.setText(1, attributes.value('type'))
        self._text = ''
        return True

    def endElement(self, namespace, name, qname):
        if qname == 'title':
            if self._item is not None:
                self._item.setText(0, self._text)
        elif qname == 'folder' or qname == 'item':
            self._item = self._item.parent()
        return True

    def characters(self, text):
        self._text += text
        return True

    def fatalError(self, exception):
        print('Parse Error: line %d, column %d:\n  %s' % (
              exception.lineNumber(),
              exception.columnNumber(),
              exception.message(),
              ))
        return False

    def errorString(self):
        return self._error

class Window(QtGui.QTreeWidget):
    def __init__(self):
        QtGui.QTreeWidget.__init__(self)
        self.header().setResizeMode(QtGui.QHeaderView.Stretch)
        self.setHeaderLabels(['Title', 'Type'])
        source = QtXml.QXmlInputSource()
        source.setData(xml)
        handler = XmlHandler(self)
        reader = QtXml.QXmlSimpleReader()
        reader.setContentHandler(handler)
        reader.setErrorHandler(handler)
        reader.parse(source)

xml = """\
<root>
    <folder>
        <title>Folder One</title>
        <item type="1">
            <title>Item One</title>
        </item>
        <item type="1">
            <title>Item Two</title>
        </item>
        <item type="2">
            <title>Item Three</title>
        </item>
        <folder>
            <title>Folder Two</title>
            <item type="3">
                <title>Item Four</title>
            </item>
            <item type="0">
                <title>Item Five</title>
            </item>
            <item type="1">
                <title>Item Six</title>
            </item>
        </folder>
    </folder>
    <folder>
        <title>Folder Three</title>
        <item type="0">
            <title>Item Six</title>
        </item>
        <item type="2">
            <title>Item Seven</title>
        </item>
        <item type="2">
            <title>Item Eight</title>
        </item>
    </folder>
</root>
"""

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.resize(400, 300)
    window.show()
    sys.exit(app.exec_())

You'll need to write something yourself to pull in the information from the XML file (hint: use a python XML parser likeElementTree ).您需要自己编写一些内容来从 XML 文件中提取信息(提示:使用 python XML 解析器,如ElementTree )。 This code will build a tree widget with the rows (and subrows) filled out from data .此代码将构建一个树小部件,其中包含从data填充的行(和子行)。

class Tree(QtGui.QTreeWidget):

    def __init__(self, parent):
        # maybe init your data here too
        super(Tree, self).__init__(parent)

    def populate(self, data):
        # populate the tree with QTreeWidgetItem items
        for row in data:
            # is attached to the root (parent) widget
            rowItem = QtGui.QTreeWidgetItem(parent)
            rowItem.setText(0, row)
            for subRow in row:
                 # is attached to the current row (rowItem) widget
                 subRowItem = QtGui.QTreeWidgetItem(rowItem)
                 subRowItem.setText(0, subRow)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM