なんてQtなPyQt(pyqt5のインストール/グラフ作成)
2019.01.23
クロスプラットフォームアプリケーションフレームワークであるPyQtを使用して、簡単なGUIを作成してみましょう。
PyQtを使用することで、簡単なGUIを即座に作成することができます。
本記事では、PyQtを使用してGUIを作成する際の非常に基本的な書き方についてご紹介いたします。
はじめに
本記事では、PyQtの使用方法について、ごくごく基本的な部分をご紹介したいと思います。
この度、業務でPyQtを使用する機会がありましたので、PyQtについて記事を執筆させて頂きました。
ちなみに、本記事の筆者は文系の大学院(しかもフェミニスト哲学・日本女性史専攻、つまりゴリゴリの文系)出身です。
プログラミングのプの字も知らずに生きてきたということです!
そのような筆者でも、PyQtを使用することでGUIを比較的容易に作成することができています。
したがって、PyQtでのGUI開発は、プログラミングのプの字もご存知の方にとっては、朝飯前どころの騒ぎではないのではないでしょうか。
そして、そのような方々がPyQtについて日本語でドシドシ記事を書いて下さればいいなと(極めて切実に)思います。
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
PyQtとは
はじめにPyQtの概要などについて簡単にご紹介したいと思います。
PyQt
ところで、PyQtとは何なのでしょうか。
ご存知の方も多いかと思いますが、PyQtとはQtというC++のGUIツールキットをPythonで使えるようにしたものです。
筆者は全く知りませんでした!
ちなみに、Qtは公式的にはキュートと読むそうです。
筆者はずっとキューティーと読んでいました。
ですので、PyQtもパイキューティーと読んでいました。
しかし、実際のところはパイキュートと読むのでしょうか?
キュートですね。
さて、Qt自体はC++で記述しますが、PyQtを使用すればC++の知識がなくてもPythonの文法でGUIを作成することができます。
ただ、PyQtで調べたいことがある場合は、日本語で調べてもほとんど情報が出てこないと思います。
そもそも、PyQtにもQtにも日本語のリファレンスは存在しないのです!
加えて、PyQtについての情報はQtについてのそれに比べるとグッと少なくなる印象があります。
実際、「PyQt」でグーグル検索すると、約142万件検索結果が出るのに対して、「Qt」で検索すると、約1億9千7百万件もヒットします(11月16日現在)。
したがって、PyQtについて何か調べたいときには、英語でかつQtについて調べた方がよいことがあるということをお分かりいただけるかと思います。
ちょっと待って、そんなこと言ってもC++の文法なんて知らないワ…と思ったそこのあなた!
C++の文法を知らなくても、C++で書かれたコードを見ればPythonでの書き方がなんとなくわかると思います。
筆者でさえなんとなくわかるので、この世の大半のプログラマにとっては赤子の手をひねるよりも易いことでしょう!
さらに言ってしまえば、C++を勉強すればいいのです!
私はC++についてCの進化系?くらいにしか思っていませんでしたが、C++についてうっすらなんとなく勉強したところ、たとえそれがQtのコード(=C++)であっても、なんとなく知っている言語で書かれているという安心感がなんとなくあります。
加えて、オブジェクト指向についてなんとなく理解が深まった気までなんとなくするというオマケつきでした。
それに英語なんて…と思ったそこのあなた!
プログラマが学ぶべき言語堂々の第一位・英語(私調べ)なのでこればっかりは仕方ないです。
と言いつつ、実際のところプログラミング言語が習得できる方であれば、自然言語も習得可能だと思います(その逆も然り)。
そこにあるのは楽しいか楽しくないかの差であったり、これまでの教育経験や社会経験に由来する心理的抵抗の差なのではないでしょうか?
英語も所詮は人間が作った言語、すなわち道具でしかないので、なんとなくやってればそのうちなんとなくわかるようになりますなりたいです。
インストールについて
PyQtはPythonの標準パッケージではないので、別にインストールする必要があります。
pip3 install PyQt5
など、適宜インストールしてください。
ここで注意点があります!
インストール後、コードを書いて実行してもうまくいかない場合があるかもしれません。
Tkinterがインストールされていないというエラーが出ることがあるようです。
筆者はPyQt5を会社のパソコンにインストールした時にも自宅のパソコンにインストールした時にも、
Tkinterがインストールされていないというエラーが出ました(共に仮想環境上のUbuntuです)。
TkinterはPythonでGUIを作成する際の標準ライブラリのはずなのですが、インストールされていない場合があるかもしれません。
また、ちょっと調べてみたところ、PyQtに関係なくTkinter自体を使おうとした際に、同様のエラーが結構出ているようです。
それにPythonの2系か3系かで、tkinterとTkinterに表記が分かれているようなので、その辺も確認してみましょう。
Python自体の環境によって問題の原因が結構違うみたいなので、エラーが出た際にはお調べください!
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
ウィンドウを出してみよう
ということで早速、PyQtを使用して何かを作ってみましょう。
あらかじめお断りしておくと、本記事では高尚なGUIは全く作られ得ません。ごめんなさい。
また、当然のことながら、本記事に記載されているコードはあくまでも一例に過ぎません。
こんな小汚いコード書いちゃって…プププとご一読くださった方がよいでしょう。
ですので、ご自分の書き方を見つけてくださいね!
まず、PyQtで最も基本となるのは、何はともあれウィンドウそのもの表示することです。
# - * - coding: utf8 - * - import sys from PyQt5.QtWidget import QWidget, QApplication class MyWindow(QWidget): # QWidgetクラスを使用します。 def __init__(self): super().__init__() self.title = 'ウィンドウだけだよウィンドウ' self.width = 500 self.height = 400 self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(0, 0, self.width, self.height) self.show() def main(): app = QApplication(sys.argv) gui = MyWindow() sys.exit(app.exec_()) if __name__ == "__main__": main()
以上のコードを実行すると、ウィンドウだけで何の機能もないウィンドウが出現するはずです。
以上のように、ウィンドウだけのウィンドウが表示できました!やったね!
本当はもっと短いコードでも同じようにウィンドウを出すことができます。
しかし、PyQtではウィンドウを基本的にクラス化して管理します。
そのため、上記のようにクラスを使った書き方をしてみました。
この何もない生まれたてのウィンドウこそが、私たちとQtなPyQtが織りなすGUI開発という旅の第一歩となるのです…。
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
ステータスバーを表示してみよう
さて、以上で基本となるウィンドウを出すことができました。
これだけでは特に何もできませんよね。
早速、この生まれたてのウィンドウちゃんに仕事を覚えさせましょう!バブーッ!
ということでまずは、このウィンドウにステータスバーを表示させてみたいと思います。
ステータスバーには、現在のウィンドウ内の情報などを出力することが可能です。
ところで、先ほどのウィンドウを出した際に使用したクラスを思い出してください。
先ほどはQWidgetクラスを継承したMyWindowクラスを用いてウィンドウを作成しました。
しかし、ステータスバーを表示するにはQMainWindowクラスを継承してウィンドウを作成していく必要があります。
QWidgetやQDialogでもフォーム画面のような簡単なウィンドウを作成可能です。
しかし、メインのウィンドウについてはQMainWindowを使用することになるかと思います。
というのも、QMainWindowは以下の機能を提供しているためです。
・メニューバー
・ツールバー
・ドックウィジェット
・中央ウィジェット
・ステータスバー
以上のように、QMainWindowクラスはアプリケーションのメインウィンドウを作成するための基本的な構成をあらかじめ提供しているクラスとなります。
では実際にステータスバーを表示するウィンドウを作成してみましょう!
# - * - coding: utf8 - * - import sys from PyQt5.QtWidgets import QMainWindow, QApplication class MyWindow(QMainWindow): # QMainWindowクラスを使用します。 def __init__(self): super().__init__() self.title = 'QMainWindowウィンドウだよ' self.width = 500 self.height = 400 self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(0, 0, self.width, self.height) self.statusBar().showMessage('ここがステータスバーだよ') self.show() def main(): app = QApplication(sys.argv) gui = MyWindow() sys.exit(app.exec_()) if __name__ == '__main__': main()
以上のコードを実行すると、画面下部にステータスバーが表示されるはずです。
以上のように、ウィンドウの左下にステータスバーが表示されました!
ベタ打ちした文字をただ表示するだけでは面白くないですよね???
そこで、ステータスバーにデジタル時計を実装してみましょう。
# - * - coding: utf8 - * - import sys from PyQt5.QtWidgets import QMainWindow, QApplication from PyQt5.QtCore import QTimer import datetime # 時計を表示するために必要なので必ずimportしましょう。 class MyWindow(QMainWindow): def __init__(self): super().__init__() self.title = 'ステータスバー時計だよウィンドウ' self.width = 500 self.height = 400 self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(0, 0, self.width, self.height) self.show() timer = QTimer(self) timer.timeout.connect(self.getDateTime) timer.start(1000) # 1000ミリ秒 def getDateTime(self): dt = datetime.datetime.today() dt_str = dt.strftime('%Y年%m月%d日 %H時%M分%S秒') self.statusBar().showMessage( 'ステータスバーに時計が出たよ' + ' ' + dt_str ) def main(): app = QApplication(sys.argv) gui = MyWindow() sys.exit(app.exec_()) if __name__ == '__main__': main()
以上のコードを実行すると、毎秒更新される時計がステータスバーに表示されるかと思います。
画像しか添付できないのでアレなのですが、実際には毎秒更新されています!
以上のように、ステータスバーにデジタル時計を設置することができました!
ここでポイントとなる点は、MyWindowクラスのinitUIメソッド内にある次の箇所です。
timer = QTimer(self) timer.timeout.connect(self.getDateTime) timer.start(1000)
この部分ではシグナルとスロットという機能が使われています。
オブジェクトは何らかのイベントが生じた際にシグナルを発します。
それをスロットの関数(メソッド)が受け取ることでウィンドウ上の情報を更新することができます。
ここではQTimerのインスタンスを生成しました。
そしてtimeoutシグナルに対してgetDateTimeメソッドをスロットとして呼び出しています。
PyQtではそれらをconnectで結び付けていきます。簡単ですね!
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
メニューバーを表示してみよう
では次にメニューバーを設置してみましょう。
大体のウィンドウにはメニューバーがついていますよね。
# - * - coding: utf8 - * - import sys from PyQt5.QtWidgets import QMainWindow, QApplication, Qaction, qApp class MyWindow(QMainWindow): def __init__(self): super().__init__() self.title = 'メニューバーがあるウィンドウだよウィンドウ' self.width = 500 self.height = 400 self.initUI() def initUI(self): ### メニューバーアクションの定義 ### exitAction = QAction('&終了', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('GUI画面を閉じるよ') exitAction.triggered.connect(qApp.quit) menubar = self.menuBar() fileMenu = menubar.addMenu('&ファイル') fileMenu.addAction(exitAction) # fileMenuにアクションを追加します。 self.setWindowTitle(self.title) self.setGeometry(0, 0, self.width, self.height) self.statusBar() self.show() def main(): app = QApplication(sys.argv) gui = MyWindow() sys.exit(app.exec_()) if __name__ == '__main__': main()
以上のコードを実行すると、先ほどのただのウィンドウにメニューバーが追加されるはずです。
ファイルメニューの中にウィンドウを閉じる項目があります。
そちらをクリックするともちろんウィンドウを閉じることができます。
以上のようにメニューバーを表示することができました!
ファイルメニュー内の「終了」からウィンドウを閉じることができるようになります。
ここではファイルメニューだけを作成しました。
しかし、上記のような方法で、他にもメニューを追加していくことができます。
ショートカットキー(ここではCtrl+Q)も簡単に設定することができますし、
ステータスバーにキャプション(ここでは「GUI画面を閉じるよ」)を表示することもできます!
また、ここでもステータスバーに時計を表示した時と同様の仕組みが使われています。
そう、シグナルとスロットですね!
それらを用いてウィンドウ上の操作を結び付けていることがお分かりいただけるかと思います。
このようにQMainWindowを使用することで、
ステータスバーやメニューバーといった機能を簡単に実装することができるのです!
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
適当にグラフを打ってみよう
ここまででQMainWindowをはじめ、PyQtを用いてウィンドウを作成する際の基本的なコードを確認してきました。
最後の例として、PyQtとMatplotlibを使用して、ランダムなグラフを表示してみましょう!
まず簡単にMatplotlibについてご紹介いたします。
Matplotlibとは
Matplotlibとは、PythonやNumPyで使用するためのグラフ描画用ライブラリです。
Matplotlibのサイトに行っていただければお分かりいただけるかと思いますが、非常に豊富な種類のグラフを生成することができます。
しかもきれいに!
ただ、実際にグラフをきれいに描画しようと思うと、結構大変なことが多いかと思います。
ですが、本記事で使用しているくらいの簡単なグラフであれば、すぐに描画させることができます。
なお、MatplotlibはPythonの標準パッケージではないので、別にインストールする必要があります。
pip3 install matplotlib
pipコマンドなどで、適宜インストールしましょう。
また、恐らくNumPyやSciPyと合わせて使用することが多いかと思います。
必要に応じてNumPyなどもインストールしてくださいね。
まぁ、今回はどちらも使用しないのですが…。
Matplotlibについての紹介はこれだけにして、さっそく適当にグラフを打ってみましょう!
PyQt5とMatplotlibで作るランダムなグラフ①--Python Tutorialsから
今回、PyQtとMatplotlibを使用して、ランダムなグラフを生成していきたいわけですが、
その際にすでに存在しているコードを使用していきたいと思います。
こちらのチュートリアルにある、”PyQt5 Matplotlib”という個所を参考にしていきたいと思います。
このチュートリアルに記載されているウィンドウの画像を持ってくると、以下のようになります。
出典:Python Tutorials(=https://pythonspot.com/tag/pyqt5/page/2/、10月30日最終閲覧)
こちらのウィンドウに表示されているグラフが、Matplotlibによって描画されたグラフとなります。
このウィンドウを表示するためのソースコードは以下のものになります。
こちらのソースコードは、先ほどのチュートリアルから引用しています。
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, QPushButton from PyQt5.QtGui import QIcon from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.pyplot as plt import random class App(QMainWindow): def __init__(self): super().__init__() self.left = 10 self.top = 10 self.title = 'PyQt5 matplotlib example - pythonspot.com' self.width = 640 self.height = 400 self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) m = PlotCanvas(self, width=5, height=4) m.move(0,0) button = QPushButton('PyQt5 button', self) button.setToolTip('This s an example button') button.move(500,0) button.resize(140,100) self.show() class PlotCanvas(FigureCanvas): def __init__(self, parent=None, width=5, height=4, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) self.axes = fig.add_subplot(111) FigureCanvas.__init__(self, fig) self.setParent(parent) FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) self.plot() def plot(self): data = [random.random() for i in range(25)] ax = self.figure.add_subplot(111) ax.plot(data, 'r-') ax.set_title('PyQt Matplotlib Example') self.draw() if __name__ == '__main__': app = QApplication(sys.argv) ex = App() sys.exit(app.exec_())
こちらのコードを実行すると、先ほど転載した画像のようなウィンドウが出てくるわけです。
コードをご覧いただいてお分かりかと思いますが、これまでに私たちが使用してきたQMainWindowクラスが使用されていますね。
しかし、これだけでは面白くないのです!
なぜなら、このコードでは動きがないからです!
ボタンを押しても特に何も起きないですし、グラフも最初に描画されて終了です。
また、上記のコードを実行すると、コンソール上に次のようなWarningが表示されるかと思います。
MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance. warnings.warn(message, mplDeprecation, stacklevel=1)
挙動自体に影響はない(と思う)のですが、なんやかんやと警告を表示してくださっています。
この警告も出ないように修正を加えながら、もう少しだけ面白い、というかなんとなく動きのあるウィンドウを作成してみましょう。
PyQt5とMatplotlibで作るランダムなグラフ②--なんとなく動くようにしてみよう
というわけで、なんとなく動きのあるウィンドウに調節していきたいと思います!
まずは、先ほど頂いたありがたい警告を消していきましょう。
警告文を読むとaxesがどうのこうのと仰っています。
ありがたいですね…。
警告文が表示される原因は、add_subplot()が二か所に存在しているということでしょうか?
先ほどのソースコードでは、PlotCanvasクラスのコンストラクタとplotメソッドの中で、それぞれadd_subplot()を使用しています。
つまり、ここでaxesに関わる変数を何だか二回定義しているのが問題なのでは…?
恐らく、この重複が警告文の原因となっていると考えられるでしょう!
したがって、まずはplotメソッド内にある
ax = self.figure.add_subplot(111)
という一行を削除します。
次に、ただいま削除したax変数が使われている部分を修正します。
コンストラクタで定義したself.axes変数にすれば良さそうですね。
これで警告文は表示されなくなるはずです!
ありがとうエラーメッセージ!
では、いよいよこのウィンドウに何らかの動きを設定したいと思います。
以下の機能を実装してみましょう!
・メニューバー:終了メニュー
・ステータスバー:時計
・グラフの再描画/削除機能
以上です!
メニューバーとステータスバーについては、すでに確認してきたコードをそのまま使用できます。
グラフの再描画・削除機能についても、それぞれのボタンを設置し、それらのボタンを描画と削除のメソッドにclicked.connectするだけです!
以下が警告文が出ないように修正を加え、上記の三つの機能を追加したバージョンです。
なお、クラス名や変数名、テキスト文など、変更している個所があります。
# - * - coding: utf8 - * import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import QTimer from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.pyplot as plt import random # 今回は乱数を使用するのでimportします。 import datetime ############################ ## ## ウィンドウ用のクラスです。 ## ############################ class plotGraph(QMainWindow): def __init__(self): super().__init__() self.title = '適当にグラフを打つよウィンドウ' self.width = 700 self.height = 400 self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(0, 0, self.width, self.height) self.setWindowLayout() self.statusBar() def setWindowLayout(self): ### メニューバーアクションの定義 ### exitAction = QAction('&終了', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('ウィンドウを閉じるよ') exitAction.triggered.connect(qApp.quit) menubar = self.menuBar() fileMenu = menubar.addMenu('ファイル') fileMenu.addAction(exitAction) self.w = QWidget() ### 実際にグラフを打つPlotCanvasクラスのインスタンスを生成します。 ### self.m = PlotCanvas(self, width=5, height=4) ### ボタンを作成します。 ### self.plt_button = QPushButton('グラフを打つよ', self) self.plt_button.clicked.connect(self.m.plot) self.del_button = QPushButton('グラフを消すよ', self) self.del_button.clicked.connect(self.m.clear) ### GridLayoutを使用します。 ### main_layout = QGridLayout() ### GridLayoutのどこに何を配置するのか指定します。 ### main_layout.addWidget(self.m, 0, 0, 5, 4) main_layout.addWidget(self.plt_button, 0, 11, 1, 1) main_layout.addWidget(self.del_button, 0, 11, 2, 1) timer = QTimer(self) timer.timeout.connect(self.getDateTime) timer.start(1000) self.w.setLayout(main_layout) self.setCentralWidget(self.w) self.show() def getDateTime(self): dt = datetime.datetime.today() dt_str = dt.strftime('%Y年%m月%d日 %H時%M分%S秒') self.statusBar().showMessage('日時' + ' ' + dt_str) ################################## ## ## 実際にグラフを描画するクラスです。 ## ################################## class PlotCanvas(FigureCanvas): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = Figure(figsize=(width, height), dpi=dpi) self.axes = self.fig.add_subplot(111) super(PlotCanvas, self).__init__(self.fig) self.setParent(parent) FigureCanvas.setSizePolicy( self, QSizePolicy.Expanding, QSizePolicy.Expanding ) FigureCanvas.updateGeometry(self) self.plot() def plot(self): self.axes.cla() self.data = [random.random() for i in range(25)] self.axes.plot(self.data, 'r-') self.axes.set_title('PyQt5 & Matplotlib Graph') self.draw() def clear(self): self.axes.cla() self.draw() def main(): app = QApplication(sys.argv) gui = plotGraph() sys.exit(app.exec_()) if __name__ == '__main__': main()
以上のコードを実行すると、次のようなウィンドウが表示されるかと思います。
右側にあるボタンを押すと、それぞれグラフを再描画し、またグラフを削除します。
加えて、メニューバーからウィンドウを終了することができ、ステータスバーには時計が表示されています!
それだけではあるのですが、動きのあるウィンドウを作成することができました!
ここまでできているので、他にもこのウィンドウに機能を追加することができるかと思います。
例えば、メニューから描画/削除できるようにするとか、複数のグラフを表示するとか。などなど。
他にも頑張ればデジタル時計じゃなくてアナログ時計を表示することもできるはずです。
また、このコードでは、グラフに用いるデータを乱数で生成していますが、CSVファイルを読み込んだりして、手元のデータからグラフを描画できるようにすることも可能でしょう!
というか本来的にはそういう使い方をするのだと思います!
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS
おわりにーーQtなPyQtでGUI開発
以上、PyQt5を使用してGUIを作成してきました!
まだまだPyQtには様々な機能があります。
実際、上記のようにウィンドウの中に何らかの描画領域を埋め込みたい、というときには、
QGraphicsViewクラスとQGraphicsSceneクラス、QGraphicsItemクラスを合わせて使うことも多いと思います。
つまり、本記事で作成したような構造でなくても、ウィンドウに描画領域を埋め込むことができるということです。
何しろPyQtには数百を超えるクラスが存在しているのです!
実現したい動きなど、それぞれの要件に合わせて、必ずより良い構成があります。
また、PyQtを使用すると、ここまでお付き合いいただいたように、簡単なウィンドウを容易に作成することが可能です。
ただ、PyQtにはたくさんのクラスがあったり、基本的に英語で調べる必要があったりなど、実際には大変なことがあるかもしれません。
例えば、最後のグラフを表示する例では、QGridLayout()を使用してウィンドウの各ウィジェットを配置しています。
このくらいであればなんとなくイケるのですが、もう少しウィジェットが多くなると、PyQtの方で解釈してそれっぽいところに配置しようとしている(?)ので思うようにレイアウトすることが難しいこともあります(恐らくこれはPyQtへの逆恨みなのでしょう…)。
実際に使ってみると、何よこれ!全然キュートじゃないじゃない!と思うことがあるのも事実です。
ですが何はともあれ、GUIを作成する必要がある皆様には、PyQtという選択肢があるよということをお伝えできていればオールオッケーです。
QtなPyQtとの良きGUI開発を! Adieu!
>
AWS、Google Cloud、Azure の利用料が、今なら最大8%OFF!
>
各種クラウド・オンプレの「導入支援・監視運用」なら JIG-SAW OPS