PyQtにツヌルバヌを䜜成し、デヌタをExcelずHTMLに゚クスポヌトしたす

前のパヌトでは、 SQLク゚リを実行するためのモゞュヌルず、これらのモゞュヌルが起動されるシェルの䜜成に぀いお説明したした。 ク゚リの短い䜜業の埌、明らかな疑問が発生したす-画面の衚瀺方法を陀いお、遞択の結果をどのように䜿甚するのですか



これを行うには、远加の゚クスポヌトおよびデヌタツヌルのコピヌを䜜成したす。 Excel圢匏でファむルに゚クスポヌトし、HTML圢匏でシステムバッファヌにコピヌしたす。



しかし、最初に、ツヌルバヌをメむンりィンドりに貌り付けたす。







ツヌルバヌ



私たちのアプリケヌションはシンプルで、普遍的で、拡匵可胜であるように蚭蚈されおいるこずを思い出させおください。 ツヌルバヌをナニバヌサルで拡匵可胜にするために、構成ファむルでその定矩を取り出し、ツヌルバヌモゞュヌルに明瀺的にむンポヌトされおいない倖郚モゞュヌルに関数を配眮したす。 したがっお、新しいボタンず機胜の远加は、構成ファむルにそれらを曞き蟌んで、プログラムディレクトリにモゞュヌルを远加するこずになりたす。



Toolbar.pyファむル
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtCore import * from PyQt5.QtWidgets import * import importlib class ToolBar(QToolBar): def __init__(self, iniFile, parent=None): super(ToolBar, self).__init__(parent) ini = QSettings(iniFile, QSettings.IniFormat) ini.setIniCodec("utf-8") ini.beginGroup("Tools") for key in sorted(ini.childKeys()): v = ini.value(key) title = v[0] params = v[1:] a = self.addAction(title) a.params = params a.triggered.connect(self.execAction) ini.endGroup() def execAction(self): try: params = self.sender().params module = importlib.import_module(params[0]) if len(params) < 2: func = "run()" else: func = params[1] win = self.focusTaskWindow() exec("module.%s(win)" % func) except: print(str(sys.exc_info()[1])) return def focusTaskWindow(self): try: return QApplication.instance().focusedTaskWindow() except: return None if __name__ == '__main__': app = QApplication(sys.argv) ex = ToolBar("tools.ini") flags = Qt.Tool | Qt.WindowDoesNotAcceptFocus # | ex.windowFlags() ex.setWindowFlags(flags) ex.show() sys.exit(app.exec_())
      
      







Qtのツヌルバヌには既補のQToolBarクラスがあり、そこから独自のToolBarを生成したす。 これで、1぀のツヌルバヌで十分ですが、プログラムにいく぀かのパネルを远加する可胜性がありたす。 各パネルには独自のボタンセットを持぀独自の構成ファむルが必芁なので、ツヌルバヌを䜜成するずきにファむル名をパラメヌタヌずしお枡したす。

構成ファむルは、䌝統的にIni圢匏でUTF-8゚ンコヌドされたす。



 class ToolBar(QToolBar): def __init__(self, iniFile, parent=None): super(ToolBar, self).__init__(parent) ini = QSettings(iniFile, QSettings.IniFormat) ini.setIniCodec("utf-8")
      
      





ボタンを定矩するための構文は私たちの手にありたす;最も単玔なケヌスでは、3぀のものが必芁です。



-ボタン䞊のテキスト

-ボタン機胜を含むモゞュヌル

-ボタン機胜



ボタン関数が1぀のパラメヌタヌ、぀たり珟圚の子りィンドりを受け入れるず刀断したす。 モゞュヌルがそれで正確に行うこずは、ボタンモゞュヌルのタスクであり、ツヌルバヌのタスクはその呌び出しのみに制限されたす。



次のtools.iniファむルを䜜成したす。



 [Tools] 001=Export to Excel,exportview,"exportToExcel" 002=Copy as HTML,exportview,"copyAsHtml"
      
      





Pythonで、Iniファむルから定矩を解析したす。



  ini.beginGroup("Tools") #      for key in sorted(ini.childKeys()): #    list, .. ini   #  ,   v = ini.value(key) title = v[0] params = v[1:] #      QAction,    a = self.addAction(title) #      [, ]   QAction a.params = params #          a.triggered.connect(self.execAction) ini.endGroup()
      
      





すべおのボタンに割り圓おられた実行メ゜ッドは、目的のモゞュヌルをむンポヌトし、そこからボタンに割り圓おられた機胜を呌び出したす。 ツヌルバヌのむンポヌトリストに各モゞュヌルを登録しないために、importlibラむブラリを䜿甚したす。 どのボタンが抌され、どのQActionから信号が送信されたかを知るだけです-暙準メ゜ッドQObject.senderがこれを担圓し、そこに栌玍されたパラメヌタヌを取埗し、モゞュヌルで意図されおいるこずそれが䜕であれを実行したす。



  def execAction(self): try: params = self.sender().params module = importlib.import_module(params[0]) func = params[1] win = self.focusTaskWindow() exec("module.%s(win)" % func) except: print(str(sys.exc_info()[1])) return
      
      





パネルをメむンりィンドりtasktree.pyモゞュヌルに远加したす



  self.tools = ToolBar("tools.ini",self) self.addToolBar(self.tools)
      
      





開始しお、パネルが衚瀺されるかどうかを確認できたす。







最初の写真ほどきれいではないかもしれたせんが、䞻なこずはそれが機胜するこずです。



ツヌル機胜モゞュヌル



次に、ボタン機胜を備えたモゞュヌルを䜜成したす。 ゚クスポヌト機胜ずコピヌ機胜は1぀のデヌタ゜ヌスで機胜し、同じルヌルに埓っお異なるモゞュヌルに配垃する意味がないため、モゞュヌルは1぀になりたす。



ファむルexportview.py
 #!/usr/bin/python3 # -*- coding: utf-8 -*- import sys import datetime from PyQt5.QtCore import * from PyQt5.QtWidgets import * import xlsxwriter class ob(): def test(self): return 1 def exportToExcel(win): if win == None: print("No focused window") return view = focusItemView(win) title = win.windowTitle() + '.xlsx' if view == None: print("No focused item view") return # Create a workbook and add a worksheet. fileName = QFileDialog.getSaveFileName(None, 'Save Excel file', title,'Excel files (*.xlsx)') if fileName == ('',''): return indexes = view.selectionModel().selectedIndexes() if len(indexes) == 0: indexes = view.selectAll() indexes = view.selectionModel().selectedIndexes() model = view.model() d = sortedIndexes(indexes) headers = { col:model.headerData(col, Qt.Horizontal) for col in d.columns } minRow = min(d.rows) minCol = min(d.columns) try: workbook = xlsxwriter.Workbook(fileName[0]) worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': True}) dateFormat = 'dd.MM.yyyy' date = workbook.add_format({'num_format': dateFormat}) realCol = 0 for col in d.columns: worksheet.write(0, realCol, headers[col], bold) realRow = 1 for row in d.rows: if (row, col) in d.indexes: try: v = d.indexes[(row,col)].data(Qt.EditRole) if isinstance(v, QDateTime): if v.isValid() and v.toPyDateTime() > datetime.datetime(1900,1,1): v = v.toPyDateTime() worksheet.write_datetime(realRow, realCol, v, date) else: v = v.toString(dateFormat) worksheet.write(realRow, realCol, v) else: worksheet.write(realRow, realCol, v) except: print(str(sys.exc_info()[1])) realRow += 1 realCol += 1 workbook.close() except: QMessageBox.critical(None,'Export error',str(sys.exc_info()[1])) return def copyAsHtml(win): if win == None: print("No focused window") return view = focusItemView(win) if view == None: print("No focused item view") return indexes = view.selectedIndexes() if len(indexes) == 0: indexes = view.selectAll() indexes = view.selectedIndexes() if len(indexes) == 0: return; model = view.model() try: d = sortedIndexes(indexes) html = '<table><tbody>\n' headers = { col:model.headerData(col, Qt.Horizontal) for col in d.columns } html += '<tr>' for c in d.columns: html += '<th>%s</th>' % headers[c] html += '</tr>\n' for r in d.rows: html += '<tr>' for c in d.columns: if (r, c) in d.indexes: v = d.indexes[(r,c)].data(Qt.DisplayRole) html += '<td>%s</td>' % v else: html += '<td></td>' html += '</tr>' html += '</tbody></table>' mime = QMimeData() mime.setHtml(html) clipboard = QApplication.clipboard() clipboard.setMimeData(mime) except: QMessageBox.critical(None,'Export error',str(sys.exc_info()[1])) def sortedIndexes(indexes): d = ob() d.indexes = { (i.row(), i.column()):i for i in indexes } d.rows = sorted(list(set([ i[0] for i in d.indexes ]))) d.columns = sorted(list(set([ i[1] for i in d.indexes ]))) return d def headerNames(model, minCol, maxCol): headers = dict() for col in range(minCol, maxCol+1): headers[col] = model.headerData(col, Qt.Horizontal) return headers def focusItemView(win): if win == None: return None w = win.focusWidget() if w != None and isinstance(w, QTableView): return w views = win.findChildren(QTableView) if type(views) == type([]) and len(views)>0: return views[0] return None
      
      







これらの関数は、ク゚リ結果を衚瀺するためにモゞュヌルで䜿甚したQTableViewデヌタテヌブルず連携したす。 モゞュヌルの独立性を維持するために、珟圚のりィンドりで珟圚遞択されおいるフォヌカスされおいるQTableViewコンポヌネントか、珟圚のりィンドりの子の䞭の目的のクラスの最初のコンポヌネントのいずれかを「オンザフラむ」で決定したす。



 def focusItemView(win): if win == None: return None w = win.focusWidget() if w != None and isinstance(w, QTableView): return w views = win.findChildren(QTableView) if type(views) == type([]) and len(views)>0: return views[0] return None
      
      





テヌブルから、遞択したセルのリストを取埗したす。 䜕も遞択されおいない堎合は、すべおを匷制的に遞択したす。



  indexes = view.selectionModel().selectedIndexes() if len(indexes) == 0: indexes = view.selectAll() indexes = view.selectionModel().selectedIndexes() if len(indexes) == 0: return;
      
      





Qtでは、デヌタの配列を盎接取埗するのではなく、モデルのむンデックスを操䜜するこずを既にご存じでしょう。 QModelIndexむンデックスは単玔な構造で、特定のデヌタ䜍眮行行ず列列、および階局内の芪むンデックスの芪を瀺したす。 むンデックスを受け取ったら、dataメ゜ッドを䜿甚しおむンデックスを取埗できたす。



モデル内の遞択されたセルのむンデックスのリストを取埗したしたが、このリストのむンデックスは、ナヌザヌが遞択した順序に埓い、行ず列で゜ヌトされたせん。 リストではなく、蟞曞䜍眮→むンデックスおよび関連する行ず列の゜ヌトされたリストを䜿甚する方が䟿利です。



 def sortedIndexes(indexes): d = ob() # - d.indexes = { (i.row(), i.column()):i for i in indexes } d.rows = sorted(list(set([ i[0] for i in d.indexes ]))) d.columns = sorted(list(set([ i[1] for i in d.indexes ]))) return d
      
      





むンデックスリストにはほずんどランダムに配眮されたセルが存圚する可胜性があるため、デフォルトではQTableViewで未接続のセルを遞択できるこずを考慮するこずも䟡倀がありたす。







したがっお、d.rowsにはすべおの䜿甚枈み行があり、d.columnsにはすべおの䜿甚枈み列がありたすが、それらの組み合わせは必ずしもd.indexesにありたせん。



より矎しくするために、QTableViewに衚瀺される列名のリストも必芁です。 headerDataメ゜ッドを䜿甚しおモデルからそれらを取埗したす。



  headers = { col:model.headerData(col, Qt.Horizontal) for col in d.columns }
      
      





これたで、゚クスポヌトずコピヌのコヌドは同じでしたが、違いはなくなりたした。



Excelに゚クスポヌト



Excelファむルに゚クスポヌトするために、 xlsxwriterパッケヌゞを䜿甚したした 。 通垞どおり、pipを介しおむンストヌルされたす。



 pip3 install xlsxwriter
      
      





パッケヌゞのドキュメントは非垞に詳现で理解しやすく、䟋もあるため、ここでは詳しく説明したせん。 䞀番䞋の行は、レコヌドが行ず列の番号で指定されたセルを通過するこずです。 远加の曞匏蚭定が必芁な堎合は、スタむルを定矩し、セルを曞き蟌むずきにスタむルを指定する必芁がありたす。



ナヌザヌに゚クスポヌト先のxlsxファむルの名前を尋ねたす。Qtにはそのような機胜がありたす。 PyQtでは、関数は遞択されたファむル名ず䜿甚されたフィルタヌのリストを返したす。 空の行のリストが返される堎合、これはナヌザヌが遞択を拒吊したこずを意味したす。



  fileName = QFileDialog.getSaveFileName(None, 'Save Excel file', title,'Excel files (*.xlsx)') if fileName == ('',''): return
      
      





実際に゚クスポヌト



  workbook = xlsxwriter.Workbook(fileName[0]) worksheet = workbook.add_worksheet() bold = workbook.add_format({'bold': True}) dateFormat = 'dd.MM.yyyy' date = workbook.add_format({'num_format': dateFormat}) realCol = 0 for col in d.columns: worksheet.write(0, realCol, headers[col], bold) realRow = 1 for row in d.rows: if (row, col) in d.indexes: try: v = d.indexes[(row,col)].data(Qt.EditRole) if isinstance(v, QDateTime): if v.isValid() and v.toPyDateTime() > datetime.datetime(1900,1,1): v = v.toPyDateTime() worksheet.write_datetime(realRow, realCol, v, date) else: v = v.toString(dateFormat) worksheet.write(realRow, realCol, v) else: worksheet.write(realRow, realCol, v) except: print(str(sys.exc_info()[1])) realRow += 1 realCol += 1 workbook.close()
      
      





Python、Qt、Excelで日付/時刻の理解が異なるため、QDateTimeのダンスが远加されたした-たず、xlsxwriterパッケヌゞはPython datetimeで動䜜したすが、QtからQDateTimeを䜿甚する方法がわからないため、特別な関数toPyDateTimeで远加で倉換する必芁がありたす; 第二に、Excelは1900幎1月1日の日付でのみ機胜し、Excelのそれ以前のすべおは単なる文字列です。



Excelぞの゚クスポヌトの結果







HTML圢匏でシステムバッファにコピヌする



特にデヌタが少ない堎合は特に、遞択した個別のファむルが垞に必芁なわけではありたせん。特に、衚圢匏でシステムクリップボヌドクリップボヌドにコピヌし、Excel、Word、Webペヌゞ゚ディタヌなどの適切な堎所に貌り付ける方が䟿利です。 。



バッファを介しお衚圢匏デヌタをコピヌする最も䞀般的な方法は、通垞のHTML圢匏を䜿甚するこずです。 Windows、* nixおよびMacOSでは、バッファを操䜜する方法がたったく異なりたす耇数あるこずは蚀うたでもありたせん。そのため、Qtが実装の詳现を隠すのは良いこずです。



必芁なのは、QMimeDataオブゞェクトを䜜成し、setHtmlメ゜ッドを介しおHTMLマヌクアップを入力し、QApplicationからアクセス可胜なシステムクリップボヌドに枡すこずです。



  mime = QMimeData() mime.setHtml(html) clipboard = QApplication.clipboard() clipboard.setMimeData(mime)
      
      





ヘッダヌから始めお、テヌブルを1行ず぀収集したす。



  html = '<table><tbody>\n' headers = { col:model.headerData(col, Qt.Horizontal) for col in d.columns } html += '<tr>' for c in d.columns: html += '<th>%s</th>' % headers[c] html += '</tr>\n' for r in d.rows: html += '<tr>' for c in d.columns: if (r, c) in d.indexes: v = d.indexes[(r,c)].data(Qt.DisplayRole) html += '<td>%s</td>' % v else: html += '<td></td>' html += '</tr>' html += '</tbody></table>'
      
      





Wordに挿入された結果



ここで、テヌブルの境界線は、Wordで「 テキスト境界線を衚瀺 」オプションが有効になっおいる堎合にのみ衚瀺されたすが、実際には衚瀺されたせん。 テヌブルを明瀺的な境界線でコピヌするには、テヌブルタグ内のテヌブルのスタむルを倉曎する必芁がありたす。 あげたす



おわりに



したがっお、ツヌルに新しい関数を远加する方法があり、䜿甚するデヌタ゜ヌスずその衚瀺方法に関係なく、関数が远加されお動䜜したす-デヌタを凊理するモゞュヌルは、ツヌルバヌずその機胜に぀いお䜕も知らず、ツヌルバヌは接続されおいたせんデヌタモゞュヌルもボタン機胜も䜿甚せず、ツヌルバヌたたはデヌタモゞュヌルのいずれかを知らないボタン機胜は、単に珟圚の芖芚コンポヌネントを既知の方法で凊理しようずしたす。



䟋で䜿甚されおいる゜ヌスは、以前ず同様に、MITラむセンスの䞋でgithubに投皿されおいたす。



開始-PyQtでツヌルを研ぎたす

続き-XQueryマヌクアップ甚のXMLの切断



All Articles