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に変換して使う。

プログラムサンプル

httpd.conf簡易表示画面

httpd.confをTreeViewを使って表示する画面。ディレクティブを指定してポップアップメニューを表示するとディレクティブ説明ダイアログを表示する。httpd.confはファイルダイアログを使って選択する。以前使用したhttpd.confはメニューに履歴として残す。履歴を選択するとファイルを開く。

【起動時】

【メニューを開く】

【ファイルダイアログ】

【TreeViewにhttpd.confを表示】

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

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

プログラム

【httpdconfmain.py 本体】
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を使って作成

 # -*- 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"
 }
人気記事ランキング
ウィキ募集バナー