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"
}
