naobe @ ウィキ
PyQt
最終更新:
Bot(ページ名リンク)
-
view
ソフトウェア製品に戻る
使用したインストールファイル
PyQt4-4.10.1-gpl-Py3.3-Qt5.0.2-x32.exe
名前から見ると、PyQtは4-4.10.1、Pyrthonは、3.3、Qtは5.0.2
PyQtとは
QtのPythonラッパー。PythonでQtを使ったGUIアプリケーションを作成できる。Linuxでも同じソースで動作する(Python,Qtのバージョンが同じであれば)。ドキュメントは、<Python>/Lib/site-packages/PyQt4/doc下にある。
QtDesighner
画面作成ツール。左側にある部品を選択してWindow上に配置して、外見を確認しながら画面を作成するツール。慣れれば、コーディングするより早く画面を作成できる。
作成したファイルは、*.uicという名前になる。<Python>/Lib/site-packages/PyQt4/pyuic4.batを起動して、*.pyに変換して使う。
作成したファイルは、*.uicという名前になる。<Python>/Lib/site-packages/PyQt4/pyuic4.batを起動して、*.pyに変換して使う。
プログラムサンプル
httpd.conf簡易表示画面
httpd.confをTreeViewを使って表示する画面。ディレクティブを指定してポップアップメニューを表示するとディレクティブ説明ダイアログを表示する。httpd.confはファイルダイアログを使って選択する。以前使用したhttpd.confはメニューに履歴として残す。履歴を選択するとファイルを開く。
【起動時】

【メニューを開く】

【ファイルダイアログ】

【TreeViewにhttpd.confを表示】

【ディレクティブを選択して説明ダイアログを表示】

【メニューから以前使用したhttpd.confを選択】

プログラム
【httpdconfmain.py 本体】
PyQtのサンプルプログラム(editabletreemodel.py)を修正して作成。TreeViewのリフレッシュで苦労した。Google検索して試行錯誤のうえTreeModelのmodelresetシグナルを発行して動いた。TreeWidgetを使えば簡単らしい。
PyQtのサンプルプログラム(editabletreemodel.py)を修正して作成。TreeViewのリフレッシュで苦労した。Google検索して試行錯誤のうえTreeModelのmodelresetシグナルを発行して動いた。TreeWidgetを使えば簡単らしい。
# -*- coding: utf-8 -*- """ httpd.conf管理画面 httpd.confを読み込み表示する。 """ import sys from PyQt4.QtCore import QAbstractItemModel,QModelIndex,Qt,pyqtSlot,QPoint from PyQt4.QtGui import QWidget,QMenu,QApplication,QAction,QFileDialog,QMainWindow from gui.httpdconfmainui import * from dirDescDialog import DirDescDialog from util.HistoryFile import HistoryFile import json """ ツリーアイテム TreeViewの行に相当する """ class TreeItem(object): def __init__(self, data, parent=None): self.parentItem = parent self.itemData = data self.childItems = [] def child(self, row): return self.childItems[row] def childCount(self): return len(self.childItems) def childNumber(self): if self.parentItem != None: return self.parentItem.childItems.index(self) return 0 def columnCount(self): return len(self.itemData) def data(self, column): return self.itemData[column] """ 子領域を作成する @param psition 子配列の追加する子領域位置 @param count 行数 @param columns 列数 """ def insertChildren(self, position, count, columns): if position < 0 or position > len(self.childItems): return False for row in range(count): data = [None for v in range(columns)] item = TreeItem(data, self) self.childItems.insert(position, item) return True def insertColumns(self, position, columns): if position < 0 or position > len(self.itemData): return False for column in range(columns): self.itemData.insert(position, None) for child in self.childItems: child.insertColumns(position, columns) return True def parent(self): return self.parentItem def removeChildren(self, position, count): if position < 0 or position + count > len(self.childItems): return False for row in range(count): self.childItems.pop(position) return True def removeColumns(self, position, columns): if position < 0 or position + columns > len(self.itemData): return False for column in range(columns): self.itemData.pop(position) for child in self.childItems: child.removeColumns(position, columns) return True def setData(self, column, value): if column < 0 or column >= len(self.itemData): return False self.itemData[column] = value return True """ ツリーモデル """ class TreeModel(QAbstractItemModel): def __init__(self, headers, parent=None): super(TreeModel, self).__init__(parent) self.rootData = [header for header in headers] self.rootItem = TreeItem(self.rootData) # 親スタック self.parents = [self.rootItem] #self.setupModelData(data.split("\n"), self.rootItem) """ ヘッダ以外のデータをクリア """ def clearData(self): self.rootItem = TreeItem(self.rootData) self.parents = [self.rootItem] pass def columnCount(self, parent=QModelIndex()): return self.rootItem.columnCount() def data(self, index, role): if not index.isValid(): return None if role != Qt.DisplayRole and role != Qt.EditRole: return None item = self.getItem(index) return item.data(index.column()) def flags(self, index): if not index.isValid(): return 0 return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def getItem(self, index): if index.isValid(): item = index.internalPointer() if item: return item return self.rootItem def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.rootItem.data(section) return None def index(self, row, column, parent=QModelIndex()): if parent.isValid() and parent.column() != 0: return QModelIndex() parentItem = self.getItem(parent) childItem = parentItem.child(row) if childItem: return self.createIndex(row, column, childItem) else: return QModelIndex() def insertColumns(self, position, columns, parent=QModelIndex()): self.beginInsertColumns(parent, position, position + columns - 1) success = self.rootItem.insertColumns(position, columns) self.endInsertColumns() return success def insertRows(self, position, rows, parent=QModelIndex()): parentItem = self.getItem(parent) self.beginInsertRows(parent, position, position + rows - 1) success = parentItem.insertChildren(position, rows, self.rootItem.columnCount()) self.endInsertRows() return success def parent(self, index): if not index.isValid(): return QModelIndex() childItem = self.getItem(index) parentItem = childItem.parent() if parentItem == self.rootItem: return QModelIndex() return self.createIndex(parentItem.childNumber(), 0, parentItem) def removeColumns(self, position, columns, parent=QModelIndex()): self.beginRemoveColumns(parent, position, position + columns - 1) success = self.rootItem.removeColumns(position, columns) self.endRemoveColumns() if self.rootItem.columnCount() == 0: self.removeRows(0, self.rowCount()) return success def removeRows(self, position, rows, parent=QModelIndex()): parentItem = self.getItem(parent) self.beginRemoveRows(parent, position, position + rows - 1) success = parentItem.removeChildren(position, rows) self.endRemoveRows() return success def rowCount(self, parent=QModelIndex()): parentItem = self.getItem(parent) return parentItem.childCount() def setData(self, index, value, role=Qt.EditRole): if role != Qt.EditRole: return False item = self.getItem(index) result = item.setData(index.column(), value) if result: self.dataChanged.emit(index, index) return result def setHeaderData(self, section, orientation, value, role=Qt.EditRole): if role != Qt.EditRole or orientation != Qt.Horizontal: return False result = self.rootItem.setData(section, value) if result: self.headerDataChanged.emit(orientation, section, section) return result """ TreeModelセットアップ @param lineData 設定ファイル行 """ def setupModelData(self, lineData): # "</"が見つかったら親をpop if lineData[0:2] == "</": self.parents.pop() # "<"が見つかったら親ノード作成 elif lineData[0:1] == "<": # '<','>'を削除 lineData = lineData[1:-1] columnData = lineData.split(" ", 1) self.insertChild(columnData) # 追加した子を親として追加 self.insertParent() else: #最初のブランクで2列に分解 columnData = lineData.split(" ", 1) # 親にノード登録 self.insertChild(columnData) """ TreeModelに子ノードを登録する @param columnData 列データ(配列) """ def insertChild(self, columnData): parentItem = self.parents[-1] # 親ノードに子供の領域を作成する parentItem.insertChildren(parentItem.childCount(), 1, self.rootItem.columnCount()) # 子供の領域にデータを設定 for column in range(len(columnData)): parentItem.child(parentItem.childCount() -1).setData(column, columnData[column]) """ TreeModelに親ノード登録 """ def insertParent(self): parents = self.parents # 最後に登録した親 parent = parents[-1] parents.append( parent.child( parent.childCount() - 1) ) """ httpd.conf GUIクラス """ class HttpdConfMain(Ui_MainWindow, QMainWindow): # 計算機のクラス(上のGUIクラスを継承) """ コンストラクタ """ def __init__(self): super(QWidget, self ).__init__() self.setupUi(self) # GUIを設定(これがないとボタンなどが配置されない) # メニューバー設定 #-- アクション rfAction = QAction("開く(&o)", self) # 設定ファイル読み込み exitAction = QAction("終了(&x)", self) #-- ファイルメニュー self.fileMenu = self.menubar.addMenu("ファイル(&F)") self.fileMenu.addAction(rfAction) rmenu = self.fileMenu.addMenu("最近開いたファイル(&r)") self.fileMenu.addAction(exitAction) #-- 最近開いたファイル self.hfile = HistoryFile("hist.dat", 5) for f in self.hfile.fileArray: rcAction = QAction(f, self) rcAction.triggered.connect(self.readMenuFile) rmenu.addAction(rcAction) # TreeModel作成 headers = ("ディレクティブ", "属性") self.treeModel = TreeModel(headers) # TreeViewにモデルをセット self.treeView.setModel(self.treeModel) # 1列目の幅を設定 self.treeView.setColumnWidth(0, 200) # ポップアップメニュー作成用設定 self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) # ヘッダをセンタリング self.treeView.header().setDefaultAlignment(Qt.AlignHCenter) # ディレクティブ説明ファイル読み込み self.dirDescMap = self.readDirDesc() # シグナル・スロット #-- ファイルメニュー選択でファイル選択ダイアログ表示 rfAction.triggered.connect(self.selectFile) #-- 終了選択でアプリケーション終了 exitAction.triggered.connect(self.exitWin) #-- 右クリックによるポップアップメニュー起動シグナル用スロット self.treeView.customContextMenuRequested.connect(self.showPopUpMenu) """ ポップアップメニュー起動 @param pos シグナル発生位置(QPoint) """ @pyqtSlot(QPoint) def showPopUpMenu(self, pos): # 選択したディレクティブ selectDetective = self.treeView.indexAt(pos).internalPointer().data(0) # スクロールエリア内だとposは、エリア内の相対位置になるためグローバルポジションに変換する。 globalPos = self.treeView.mapToGlobal(pos); myMenu = QMenu(self) action = myMenu.addAction("説明") action.setData(selectDetective) # 選択が終了するまで待つ? selectedItem = myMenu.exec(globalPos) # 選択されていたら説明ダイアログを起動する if selectedItem: dialog = DirDescDialog() if not selectedItem.data() in self.dirDescMap: dialog.textEdit.setText("設定なし") else: dialog.textEdit.setText(self.dirDescMap[selectedItem.data()]) # ダイアログの応答結果 result = dialog.exec() #print("result: %d" % result) """ ファイルダイアログを表示してファイルを選択する。 選択したファイルを読み込む """ @pyqtSlot() def selectFile(self): fd = QFileDialog(self) self.fileName = fd.getOpenFileName() if self.fileName == '': return # ファイル履歴にファイル名を保管 self.hfile.insert(self.fileName) self.readConf() """ メニューの「最近開いたファイル」で選択したファイルを読み込む """ @pyqtSlot() def readMenuFile(self): # 送り元のアクションを取得する rcAction = self.sender() print("rcAction text: %s" % rcAction.text()) self.fileName = rcAction.text() if self.fileName == '': return self.readConf() """ httpd.confを読み込む """ def readConf(self): # データクリア self.treeModel.clearData() # TreeViewリフレッシュのため self.treeModel.modelReset.emit() # 読み込み f = open(self.fileName, "r") while True: line = f.readline() # EOFに到達したら終了 if not line: break; # 改行を削除 line = line[:-1] line = line.strip().rstrip() # コメントスキップ if line[0:1] == "#": continue # 空行スキップ if len(line) == 0: continue # 設定行をモデルへ挿入 self.treeModel.setupModelData(line) f.close() """ ウィンドウを終了する """ def exitWin(self): # ファイル名を保管する self.hfile.close() app.quit() win.close() # 不要なはずだが?? """ ディレクティブ設定ファイル読み込み """ def readDirDesc(self): f = open("../conf/dirDesc.conf", "r", encoding="utf8") fullText = "" while True: # ファイルから1行読み込み line = f.readline() # コメントは飛ばす if line[0:1] == "#": continue # EOFに到達したら終了 if not line: break; fullText = fullText + line f.close() return json.loads(fullText) if __name__ == "__main__": app = QApplication(sys.argv) win = HttpdConfMain() win.show() sys.exit(app.exec_())
【httpdconfmainui.py UIクラス】
QtDesignerを使って作成
QtDesignerを使って作成
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'httpdconfmain.ui' # # Created: Tue Jun 11 23:56:11 2013 # by: PyQt4 UI code generator 4.10.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(660, 582) self.centralwidget = QtGui.QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.treeView = QtGui.QTreeView(self.centralwidget) self.treeView.setObjectName(_fromUtf8("treeView")) self.verticalLayout.addWidget(self.treeView) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtGui.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 660, 21)) self.menubar.setObjectName(_fromUtf8("menubar")) MainWindow.setMenuBar(self.menubar) self.statusbar = QtGui.QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
【dirDescDialogui.py ダイアログUI】
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dirDescDialog.ui' # # Created: Sat Jun 1 10:11:59 2013 # by: PyQt4 UI code generator 4.10.1 # # WARNING! All changes made in this file will be lost! """ ディレクティブ説明ダイアログ """ from PyQt4 import QtCore, QtGui def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_dirDesc(object): def setupUi(self, dirDesc): dirDesc.setObjectName(_fromUtf8("dirDesc")) dirDesc.resize(331, 178) dirDesc.setModal(True) self.verticalLayout = QtGui.QVBoxLayout(dirDesc) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.textEdit = QtGui.QTextEdit(dirDesc) self.textEdit.setObjectName(_fromUtf8("textEdit")) self.verticalLayout.addWidget(self.textEdit) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.closeButton = QtGui.QPushButton(dirDesc) self.closeButton.setObjectName(_fromUtf8("close")) self.horizontalLayout.addWidget(self.closeButton) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(dirDesc) QtCore.QMetaObject.connectSlotsByName(dirDesc) def retranslateUi(self, dirDesc): dirDesc.setWindowTitle(_translate("dirDesc", "Dialog", None)) self.closeButton.setText(_translate("dirDesc", "閉じる", None))
【HistoryFile.py 履歴ファイルユーティリティ】
# -*- coding: utf-8 -*- """ Created on 2013/06/14 @author: """ """ 履歴ファイルユーティリティ 履歴ファイルを作成し、履歴を追加する。 履歴は保管個数を持ち、古い履歴から削除する。 """ from os.path import isfile class HistoryFile: """ コンストラクタ @param fileName 履歴ファイル名 @param cnt 保管ファイル個数 """ def __init__(self, fileName, cnt): self.fileName = fileName # 履歴ファイル名 self.cnt = cnt # 保管ファイル個数 self.fileArray = [] # ファイル履歴 # 履歴ファイル有無を確認 if isfile(self.fileName): # ファイル履歴に保管 file = open(self.fileName, "r", encoding="utf-8") while True: # ファイルから1行読み込み line = file.readline() # EOFに到達したら終了 if not line: break; # 改行を削除 line = line[:-1] # ファイル名を保管 self.fileArray.append(line) file.close() """ ファイル履歴にファイル名を追加する @param fileName 追加するファイル名 """ def insert(self, fileName): # ファイル履歴から指定したファイル名を除く self.fileArray = [ f for f in self.fileArray if f != fileName] # 指定個数なら先頭を削除 if len(self.fileArray) == self.cnt: self.fileArray = self.fileArray[1:] # 指定したファイル名を追加 self.fileArray.append(fileName) """ ファイル履歴を取得する """ def getFileArray(self): return self.fileArray """ 履歴ファイルにファイル履歴を書き込んで閉じる """ def close(self): # 履歴ファイルを作成する file = open(self.fileName, "w", encoding="utf-8") for fname in self.fileArray: print(fname, file=file) file.close() if __name__ == "__main__": hist = HistoryFile("hist.dat", 5) hist.insert("a1") hist.insert("a2") hist.insert("a3") hist.insert("a4") hist.insert("a5") hist.insert("a6") hist.close()
【dirDesc.conf ディレクティブ説明ファイル】
# # Apache 2.4.7 ディレクティブ説明 # { "ServerRoot" : "ServerRoot directory-path\n\nインストールしたサーバのベースディレクトリ", "Listen" : "Listen [IP-address:]portnumber [protocol]\n\nサーバが listen するIP アドレスと ポート番号", "LoadModule" : "LoadModule module filename\n\nオブジェクトファイルやライブラリをリンクし、使用モジュー ルの リストに追加する", "IfModule" : "<IfModule [!]module-file|module-identifier> ... </IfModule>\n\nモジュールの存在 するかしないかに応じて処理される ディレクティブを囲む。\n\nmodule-fileはServerRootからの相対ディレクトリで示す。module -identifierは、モジュールソースファイルのmod_を除いた部分に_moduleを追加したもの。\n【例】mod_auth_basic.c ==> auth_ba sic_module" }