关联数据项选择的一个解决方案就是使用树视图。本文通过Python3+pyqt5实现了python Qt GUI 快速编程的16章的树视图例子。
/home/yrd/eric_workspace/chap16/treeoftable.py
#!/usr/bin/env python3
import bisect
import codecs
from PyQt5.QtCore import (QAbstractItemModel, QModelIndex,QVariant, Qt)
KEY, NODE = range(2)
class BranchNode(object):
def __init__(self, name, parent=None):
super(BranchNode, self).__init__()
self.name = name
self.parent = parent
self.children = []
def __lt__(self, other):
if isinstance(other, BranchNode):
return self.orderKey() < other.orderKey()
return False
def orderKey(self):
return self.name.lower()
def toString(self):
return self.name
def __len__(self):
return len(self.children)
def childAtRow(self, row):
assert 0 <= row < len(self.children)
return self.children[row][NODE]
def rowOfChild(self, child):
for i, item in enumerate(self.children):
if item[NODE] == child:
return i
return -1
def childWithKey(self, key):
if not self.children:
return None
# Causes a -3 deprecation warning. Solution will be to
# reimplement bisect_left and provide a key function.
i = bisect.bisect_left(self.children, (key, None))
if i < 0 or i >= len(self.children):
return None
if self.children[i][KEY] == key:
return self.children[i][NODE]
return None
def insertChild(self, child):
child.parent = self
bisect.insort(self.children, (child.orderKey(), child))
def hasLeaves(self):
if not self.children:
return False
return isinstance(self.children[0], LeafNode)
class LeafNode(object):
def __init__(self, fields, parent=None):
super(LeafNode, self).__init__()
self.parent = parent
self.fields = fields
def orderKey(self):
return "\t".join(self.fields).lower()
def toString(self, separator="\t"):
return separator.join(self.fields)
def __len__(self):
return len(self.fields)
def asRecord(self):
record = []
branch = self.parent
while branch is not None:
record.insert(0, branch.toString())
branch = branch.parent
assert record and not record[0]
record = record[1:]
return record + self.fields
def field(self, column):
assert 0 <= column <= len(self.fields)
return self.fields[column]
class TreeOfTableModel(QAbstractItemModel):
def __init__(self, parent=None):
super(TreeOfTableModel, self).__init__(parent)
self.columns = 0
self.root = BranchNode("")
self.headers = []
def load(self, filename, nesting, separator):
self.beginResetModel()
assert nesting > 0
self.nesting = nesting
self.root = BranchNode("")
exception = None
fh = None
try:
for line in codecs.open(str(filename), "rU", "utf8"):
if not line:
continue
self.addRecord(line.split(separator), False)
except IOError as e:
exception = e
finally:
if fh is not None:
fh.close()
#self.reset()
self.endResetModel()
for i in range(self.columns):
self.headers.append("Column #{0}".format(i))
if exception is not None:
raise exception
def addRecord(self, fields, callReset=True):
assert len(fields) > self.nesting
root = self.root
branch = None
for i in range(self.nesting):
key = fields[i].lower()
branch = root.childWithKey(key)
if branch is not None:
root = branch
else:
branch = BranchNode(fields[i])
root.insertChild(branch)
root = branch
assert branch is not None
items = fields[self.nesting:]
self.columns = max(self.columns, len(items))
branch.insertChild(LeafNode(items, branch))
if callReset:
self.beginResetModel()
self.endResetModel()
def asRecord(self, index):
leaf = self.nodeFromIndex(index)
if leaf is not None and isinstance(leaf, LeafNode):
return leaf.asRecord()
return []
def rowCount(self, parent):
node = self.nodeFromIndex(parent)
if node is None or isinstance(node, LeafNode):
return 0
return len(node)
def columnCount(self, parent):
return self.columns
def data(self, index, role):
if role == Qt.TextAlignmentRole:
return QVariant(int(Qt.AlignTop|Qt.AlignLeft))
if role != Qt.DisplayRole:
return QVariant()
node = self.nodeFromIndex(index)
assert node is not None
if isinstance(node, BranchNode):
return node.toString() if index.column() == 0 else ""
return node.field(index.column())
def headerData(self, section, orientation, role):
if (orientation == Qt.Horizontal and
role == Qt.DisplayRole):
assert 0 <= section <= len(self.headers)
return self.headers[section]
return QVariant()
def index(self, row, column, parent):
assert self.root
branch = self.nodeFromIndex(parent)
assert branch is not None
return self.createIndex(row, column,
branch.childAtRow(row))
def parent(self, child):
node = self.nodeFromIndex(child)
if node is None:
return QModelIndex()
parent = node.parent
if parent is None:
return QModelIndex()
grandparent = parent.parent
if grandparent is None:
return QModelIndex()
row = grandparent.rowOfChild(parent)
assert row != -1
return self.createIndex(row, 0, parent)
def nodeFromIndex(self, index):
return (index.internalPointer()
if index.isValid() else self.root)
/home/yrd/eric_workspace/chap16/serverinfo.pyw
#!/usr/bin/env python3
import os
import sys
from PyQt5.QtCore import (QModelIndex, QVariant, Qt,pyqtSignal)
from PyQt5.QtWidgets import (QApplication, QMainWindow,QMessageBox, QShortcut, QTreeView)
from PyQt5.QtGui import QKeySequence,QPixmap
import treeoftable
class ServerModel(treeoftable.TreeOfTableModel):
def __init__(self, parent=None):
super(ServerModel, self).__init__(parent)
def data(self, index, role):
if role == Qt.DecorationRole:
node = self.nodeFromIndex(index)
if node is None:
return QVariant()
if isinstance(node, treeoftable.BranchNode):
if index.column() != 0:
return QVariant()
filename = node.toString().replace(" ", "_")
parent = node.parent.toString()
if parent and parent != "USA":
return QVariant()
if parent == "USA":
filename = "USA_" + filename
filename = os.path.join(os.path.dirname(__file__),
"flags", filename + ".png")
pixmap = QPixmap(filename)
if pixmap.isNull():
return QVariant()
return QVariant(pixmap)
return treeoftable.TreeOfTableModel.data(self, index, role)
class TreeOfTableWidget(QTreeView):
activated_signal=pyqtSignal(list)
def __init__(self, filename, nesting, separator, parent=None):
super(TreeOfTableWidget, self).__init__(parent)
self.setSelectionBehavior(QTreeView.SelectItems)
self.setUniformRowHeights(True)
model = ServerModel(self)
self.setModel(model)
try:
model.load(filename, nesting, separator)
except IOError as e:
QMessageBox.warning(self, "Server Info - Error", str(e))
self.activated[QModelIndex].connect(self.activate)
self.expanded.connect(self.expand)
self.expand()
def currentFields(self):
return self.model().asRecord(self.currentIndex())
def activate(self, index):
self.activated_signal.emit(self.model().asRecord(index))
def expand(self):
for column in range(self.model().columnCount(
QModelIndex())):
self.resizeColumnToContents(column)
class MainForm(QMainWindow):
def __init__(self, filename, nesting, separator, parent=None):
super(MainForm, self).__init__(parent)
headers = ["Country/State (US)/City/Provider", "Server", "IP"]
if nesting != 3:
if nesting == 1:
headers = ["Country/State (US)", "City", "Provider",
"Server"]
elif nesting == 2:
headers = ["Country/State (US)/City", "Provider",
"Server"]
elif nesting == 4:
headers = ["Country/State (US)/City/Provider/Server"]
headers.append("IP")
self.treeWidget = TreeOfTableWidget(filename, nesting,
separator)
self.treeWidget.model().headers = headers
self.setCentralWidget(self.treeWidget)
QShortcut(QKeySequence("Escape"), self, self.close)
QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
self.treeWidget.activated_signal[list].connect(self.activated)
self.setWindowTitle("Server Info")
self.statusBar().showMessage("Ready...", 5000)
def picked(self):
return self.treeWidget.currentFields()
def activated(self, fields):
self.statusBar().showMessage("*".join(fields), 60000)
app = QApplication(sys.argv)
nesting = 3
if len(sys.argv) > 1:
try:
nesting = int(sys.argv[1])
except:
pass
if nesting not in (1, 2, 3, 4):
nesting = 3
form = MainForm(os.path.join(os.path.dirname(__file__), "servers.txt"),
nesting, "*")
form.resize(750, 550)
form.show()
app.exec_()
print("*".join(form.picked()))
运行结果: