wxPythonでファイルダイアログ
wxPythonで長時間実行されるタスク中にGUIの応答性を維持する方法 - メグタンの何でもブログ の続き
wxPythonでファイルダイアログを開く
Openダイアログ
def m_menuOpenOnMenuSelection( self, event ): wildcard = "Python source (*.py)|*.py|" \ "Compiled Python (*.pyc)|*.pyc|" \ "SPAM files (*.spam)|*.spam|" \ "Egg file (*.egg)|*.egg|" \ "All files (*.*)|*.*" dlg = wx.FileDialog(self, message="Choose a file", defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, style=wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST | wx.FD_PREVIEW ) if dlg.ShowModal() == wx.ID_OK: # This returns a Python list of files that were selected. paths = dlg.GetPaths() dlg.Destroy()
Saveダイアログ
def m_menuSaveOnMenuSelection( self, event ): wildcard = "Python source (*.py)|*.py|" \ "Compiled Python (*.pyc)|*.pyc|" \ "SPAM files (*.spam)|*.spam|" \ "Egg file (*.egg)|*.egg|" \ "All files (*.*)|*.*" dlg = wx.FileDialog(self, message="Save file as ...", defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT ) # This sets the second filter as the default filter dlg.SetFilterIndex(2) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() # fp = file(path, 'w') # Create file anew # fp.write(data) # fp.close() dlg.Destroy()
wxPythonで長時間実行されるタスク中にGUIの応答性を維持する方法
wxpythonのwx.CallLater - メグタンの何でもブログ の続き 画面のボタンはStartとAbortの2つになってます。
長時間実行されるタスク中にGUIの応答性を維持する
CPUを使い続ける計算等でメインスレッドを占有すると、ウインドウが固まってしまい操作性が阻害される。
回避するには次のような方法がある。
wx.Yield()を使用する方法
wx.Yield()によって一時的に関数処理を中断しメインスレッドに他の処理をさせる。
計算ループ内で実行される頻度によって応答性は変わる。
画面の処理が行われるので、計算が終わるまで起動ボタンを無効化する。
def m_buttonStartOnButtonClick( self, event ): self.m_buttonStart.Enable(False) self.guiloop() self.m_buttonStart.Enable(True)
def guiloop(self): for i in range(0,100,1): time.sleep(0.1) # ここで時間の掛かる処理 self.m_gauge1.SetValue(i) wx.Yield()
wx.lib.delayedresultを使用する方法
import wx.lib.delayedresult as delayedresult
中断用にAbortEventを作っておく
self.abortEvent = delayedresult.AbortEvent()
self.jobID = 0
Startボタンを押した時の処理
Startボタンを無効化してAbortボタンを有効にする
def m_buttonStartOnButtonClick( self, event ): self.m_buttonStart.Enable(False) self.m_buttonAbort.Enable(True) self.abortEvent.clear() self.jobID += 1 delayedresult.startWorker(self.guiloop_finish, self.guiloop, wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
Abortボタンを押した時の処理
AbortEventをセットする
Startボタンを有効化してAbortボタンを無効化する
def m_buttonAbortOnButtonClick( self, event ): self.abortEvent.set() self.m_buttonStart.Enable(True) self.m_buttonAbort.Enable(False)
別スレッドでの重たい処理
途中経過で画面を操作したい時にはCallAfterでメインスレッドに委託
def guiloop(self,jobID, abortEvent): for i in range(0,20,1): time.sleep(0.1) # ここで時間の掛かる処理 wx.CallAfter(self.update,i) # ret = i if abortEvent(): print("Abort") break return(str(ret)) def update(self, data): print('update id: {0}'.format(th.get_ident())) #UIスレッドで実行 self.m_gauge1.SetValue(data)
別スレッドでの処理が終了した時に呼ばれる処理
Startボタンを有効化してAbortボタンを無効化する
この関数はメインスレッドで呼ばれる
Abortによる終了時には呼ばれない
def guiloop_finish(self ,delayedResult): self.m_buttonStart.Enable(True) self.m_buttonAbort.Enable(False) jobID = delayedResult.getJobID() assert jobID == self.jobID try: result = delayedResult.get() except Exception as exc: print( "Result for job %s raised exception: %s" % (jobID, exc) ) return print( "Got result for job %s: %s" % (jobID, result) )
wx.ProgressDialogで進捗の表示とキャンセルをする例
ProgressDialogでメインウインドウがロックされるのでボタンの有効/無効は不要
def m_buttonStartOnButtonClick(self, event ): self.abortEvent.clear() self.jobID += 1 delayedresult.startWorker(self.guiloop_finish, self.guiloop, wargs=(self.jobID,self.abortEvent), jobID=self.jobID) self.max = 80 self.dlg = wx.ProgressDialog("Progress dialog example", "An informative message", maximum = self.max, parent=self, style = 0 | wx.PD_APP_MODAL | wx.PD_CAN_ABORT #| wx.PD_CAN_SKIP #| wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME #| wx.PD_AUTO_HIDE )
guiloop_finishの最後でProgressDialogをDestroy
def guiloop_finish(self ,delayedResult): jobID = delayedResult.getJobID() assert jobID == self.jobID try: result = delayedResult.get() except Exception as exc: print( "Result for job %s raised exception: %s" % (jobID, exc) ) return print( "Got result for job %s: %s" % (jobID, result) ) self.dlg.Destroy()
guiloopは同じ
def guiloop(self,jobID, abortEvent): ret = 0 for i in range(0,self.max,1): time.sleep(0.1) # ここで時間の掛かる処理 if abortEvent(): print("Abort") break wx.CallAfter(self.update,i) ret = i return(str(ret))
guiloopから進捗を伝達
キャンセルボタンが押されていたらAbortしてProgressDialogをDestroy
def update(self, data): if data >= self.max / 2: (keepGoing, skip) = self.dlg.Update(data, "Half-time!") else: (keepGoing, skip) = self.dlg.Update(data) if not keepGoing: self.abortEvent.set() self.dlg.Destroy()
wxpythonのwx.CallLater
wxpythonのwx.CallAfter - メグタンの何でもブログ の続き
ディレイを入れて実行するwx.CallLater
wx.CallLaterでは指定したms後に処理を行うことができる。
自身の中で繰り返しリスタートする事でwx.Timerの様に定期実行ができる。
メインスレッド(UIスレッド)のみから実行できる。
def m_button1OnButtonClick( self, event ): self.count = 0 wx.CallLater(100, self.laterTimer)
def laterTimer(self): print('laterTimer id: {0}'.format(th.get_ident())) self.count = self.count + 1 if self.count == 100: return self.m_gauge1.SetValue(self.count) wx.CallLater(100, self.laterTimer)
wx.CallLater
wx.CallLater — wxPython Phoenix 4.2.0 documentation
wxpythonのwx.CallAfter
wxpythonのwx.Timer - メグタンの何でもブログ の続き
他のスレッドからGUI操作する時の wx.CallAfter
wx.CallAfterは処理をメインスレッド(GUIスレッド)のキューに登録して実行してくれる関数です。
他のスレッドからGUIの部品操作を行う時に使用する事が出来ます。
def m_button1OnButtonClick( self, event ): if self.thread.is_alive() == True: self.threadStop = True #スレッドを終了させる self.thread.join() #スレッドの終了を待つ else: self.thread = th.Thread(target=self.worker, daemon=True) self.thread.start()
self.thread = th.Thread(target=self.worker, daemon=True) self.threadStop = True def update(self, data): print('id: {0}'.format(th.get_ident())) #スレッドの確認(UIスレッド) self.m_gauge1.SetValue(data) def worker(self): print('id: {0}'.format(th.get_ident())) #スレッドの確認 self.threadStop = False for i in range(0,100,1): if self.threadStop == True: break time.sleep(0.5) # ここで時間の掛かる処理 wx.CallAfter(self.update,i) # GUI操作はメインスレッドから呼び出す (MainLoop内で呼び出される)
wx.CallAfterが実行されると、メインスレッドでupdateが実行されるまでサブのスレッドは停止する事になります。
実行されているスレッドを確認するには
import threading as th print('id: {0}'.format(th.get_ident()))
wx.CallAfter
wx Functions — wxPython Phoenix 4.2.0 documentation
wxpythonのwx.Timer
wxpythonのMatplotlibにラバーバンドを表示する - メグタンの何でもブログ の続き
GUIで一定周期で処理を繰り返したい時にはwx.Timerが便利
GUI上で一定周期で点滅表示とかを行いたい時にはメインスレッド(UIスレッド)で動くwx.Timerが便利
手書きで追加してもいいけれども wxFormBuilder で追加しよう。
タイマーで動かすための プログレスバーも追加
タイマーは additional の一番右に wxTimer があるので追加
プログレスバーは common の一番右に wxGage がある。
wxTimer には OnTimer イベントを追加して置く。
Generate Code で画面のコードを生成する。
このコードは修正してはいけない。
継承したクラスコードの方に、生成された OnTImer イベントをコピペして処理内容を追加する。
def m_timer1OnTimer( self, event ): self.count = self.count + 1 if self.count == 100: self.m_timer1.Stop() self.m_gauge1.SetValue(self.count)
タイマーの起動と停止の為に、ボタンクリックのイベントを記述する。
(ボタンクリックのイベント追加も上記と同じ)
ボタンを押す度にタイマーのスタート/ストップをトグルする。
def m_button1OnButtonClick( self, event ): print("button pushed") if self.m_timer1.IsRunning() == False: self.count = 0 self.m_timer1.Start(10) else: self.m_timer1.Stop()
wx.Timer の詳細は下記を参照
wx.Timer — wxPython Phoenix 4.2.0 documentation
wxpythonのMatplotlibにラバーバンドを表示する
wxpythonでmatplotlibの表示(高速化) - メグタンの何でもブログ の続き
マウスでラバーバンドを表示してmatplotlibの領域を選択したい時
matplotlibにはRectangleSelectorがあるのでそれを利用すると良い。
from matplotlib.widgets import RectangleSelector
# drawtype is 'box' or 'line' or 'none' self.toggle_selector = RectangleSelector(self.ax, self.select_callback, drawtype='box', useblit=True, button=[1], # use left button minspanx=5, minspany=5, spancoords='pixels', interactive=True)
def select_callback(self, eclick, erelease): x0, y0 = eclick.xdata, eclick.ydata x1, y1 = erelease.xdata, erelease.ydata self.p1 = [min(x0, x1), min(y0, y1)] self.p2 = [max(x0, x1), max(y0, y1)] t = 'Select Rect : (%f, %f) (%f, %f)' % (self.p1[0],self.p1[1],self.p2[0],self.p2[1]) self.m_statusBar1.SetStatusText(t)
結果はこんな感じ
wxpythonでmatplotlibの表示(高速化)
wxpythonでmatplotlibのクリック座標取得 - メグタンの何でもブログ の続き
Matplotlibの表示データが多い時に表示の負荷を減らす方法
import matplotlib as mpl
mpl.rcParams['path.simplify_threshold'] = 1.0
デフォルトでは1/9が設定されている。
値は0.0~1.0の間で設定し、0.0では表示の省略無し、1.0で省略最大。
詳しくは
Performance — Matplotlib 3.5.3 documentation
rcParamsのデフォルトを見たい時には
print(plt.rcParams)
printの結果はこんな感じ
_internal.classic_mode: False agg.path.chunksize: 0 animation.bitrate: -1 animation.codec: h264 animation.convert_args: [] animation.convert_path: convert animation.embed_limit: 20.0 animation.ffmpeg_args: [] animation.ffmpeg_path: ffmpeg animation.frame_format: png animation.html: none animation.writer: ffmpeg axes.autolimit_mode: data axes.axisbelow: line axes.edgecolor: black axes.facecolor: white axes.formatter.limits: [-5, 6] axes.formatter.min_exponent: 0 axes.formatter.offset_threshold: 4 axes.formatter.use_locale: False axes.formatter.use_mathtext: False axes.formatter.useoffset: True axes.grid: False axes.grid.axis: both axes.grid.which: major axes.labelcolor: black axes.labelpad: 4.0 axes.labelsize: medium axes.labelweight: normal axes.linewidth: 0.8 axes.prop_cycle: cycler('color', ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']) axes.spines.bottom: True axes.spines.left: True axes.spines.right: True axes.spines.top: True axes.titlecolor: auto axes.titlelocation: center axes.titlepad: 6.0 axes.titlesize: large axes.titleweight: normal axes.titley: None axes.unicode_minus: True axes.xmargin: 0.05 axes.ymargin: 0.05 axes.zmargin: 0.05 axes3d.grid: True backend: WXAgg backend_fallback: True boxplot.bootstrap: None boxplot.boxprops.color: black boxplot.boxprops.linestyle: - boxplot.boxprops.linewidth: 1.0 boxplot.capprops.color: black boxplot.capprops.linestyle: - boxplot.capprops.linewidth: 1.0 boxplot.flierprops.color: black boxplot.flierprops.linestyle: none boxplot.flierprops.linewidth: 1.0 boxplot.flierprops.marker: o boxplot.flierprops.markeredgecolor: black boxplot.flierprops.markeredgewidth: 1.0 boxplot.flierprops.markerfacecolor: none boxplot.flierprops.markersize: 6.0 boxplot.meanline: False boxplot.meanprops.color: C2 boxplot.meanprops.linestyle: -- boxplot.meanprops.linewidth: 1.0 boxplot.meanprops.marker: ^ boxplot.meanprops.markeredgecolor: C2 boxplot.meanprops.markerfacecolor: C2 boxplot.meanprops.markersize: 6.0 boxplot.medianprops.color: C1 boxplot.medianprops.linestyle: - boxplot.medianprops.linewidth: 1.0 boxplot.notch: False boxplot.patchartist: False boxplot.showbox: True boxplot.showcaps: True boxplot.showfliers: True boxplot.showmeans: False boxplot.vertical: True boxplot.whiskerprops.color: black boxplot.whiskerprops.linestyle: - boxplot.whiskerprops.linewidth: 1.0 boxplot.whiskers: 1.5 contour.corner_mask: True contour.linewidth: None contour.negative_linestyle: dashed date.autoformatter.day: %Y-%m-%d date.autoformatter.hour: %m-%d %H date.autoformatter.microsecond: %M:%S.%f date.autoformatter.minute: %d %H:%M date.autoformatter.month: %Y-%m date.autoformatter.second: %H:%M:%S date.autoformatter.year: %Y date.converter: auto date.epoch: 1970-01-01T00:00:00 date.interval_multiples: True docstring.hardcopy: False errorbar.capsize: 0.0 figure.autolayout: False figure.constrained_layout.h_pad: 0.04167 figure.constrained_layout.hspace: 0.02 figure.constrained_layout.use: False figure.constrained_layout.w_pad: 0.04167 figure.constrained_layout.wspace: 0.02 figure.dpi: 100.0 figure.edgecolor: white figure.facecolor: white figure.figsize: [6.4, 4.8] figure.frameon: True figure.max_open_warning: 20 figure.raise_window: True figure.subplot.bottom: 0.11 figure.subplot.hspace: 0.2 figure.subplot.left: 0.125 figure.subplot.right: 0.9 figure.subplot.top: 0.88 figure.subplot.wspace: 0.2 figure.titlesize: large figure.titleweight: normal font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'Sand', 'Script MT', 'Felipa', 'Comic Neue', 'Comic Sans MS', 'cursive'] font.family: ['sans-serif'] font.fantasy: ['Chicago', 'Charcoal', 'Impact', 'Western', 'Humor Sans', 'xkcd', 'fantasy'] font.monospace: ['DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Computer Modern Typewriter', 'Andale Mono', 'Nimbus Mono L', 'Courier New', 'Courier', 'Fixed', 'Terminal', 'monospace'] font.sans-serif: ['DejaVu Sans', 'Bitstream Vera Sans', 'Computer Modern Sans Serif', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'Avant Garde', 'sans-serif'] font.serif: ['DejaVu Serif', 'Bitstream Vera Serif', 'Computer Modern Roman', 'New Century Schoolbook', 'Century Schoolbook L', 'Utopia', 'ITC Bookman', 'Bookman', 'Nimbus Roman No9 L', 'Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] font.size: 10.0 font.stretch: normal font.style: normal font.variant: normal font.weight: normal grid.alpha: 1.0 grid.color: #b0b0b0 grid.linestyle: - grid.linewidth: 0.8 hatch.color: black hatch.linewidth: 1.0 hist.bins: 10 image.aspect: equal image.cmap: viridis image.composite_image: True image.interpolation: antialiased image.lut: 256 image.origin: upper image.resample: True interactive: False keymap.back: ['left', 'c', 'backspace', 'MouseButton.BACK'] keymap.copy: ['ctrl+c', 'cmd+c'] keymap.forward: ['right', 'v', 'MouseButton.FORWARD'] keymap.fullscreen: ['f', 'ctrl+f'] keymap.grid: ['g'] keymap.grid_minor: ['G'] keymap.help: ['f1'] keymap.home: ['h', 'r', 'home'] keymap.pan: ['p'] keymap.quit: ['ctrl+w', 'cmd+w', 'q'] keymap.quit_all: [] keymap.save: ['s', 'ctrl+s'] keymap.xscale: ['k', 'L'] keymap.yscale: ['l'] keymap.zoom: ['o'] legend.borderaxespad: 0.5 legend.borderpad: 0.4 legend.columnspacing: 2.0 legend.edgecolor: 0.8 legend.facecolor: inherit legend.fancybox: True legend.fontsize: medium legend.framealpha: 0.8 legend.frameon: True legend.handleheight: 0.7 legend.handlelength: 2.0 legend.handletextpad: 0.8 legend.labelcolor: None legend.labelspacing: 0.5 legend.loc: best legend.markerscale: 1.0 legend.numpoints: 1 legend.scatterpoints: 1 legend.shadow: False legend.title_fontsize: None lines.antialiased: True lines.color: C0 lines.dash_capstyle: butt lines.dash_joinstyle: round lines.dashdot_pattern: [6.4, 1.6, 1.0, 1.6] lines.dashed_pattern: [3.7, 1.6] lines.dotted_pattern: [1.0, 1.65] lines.linestyle: - lines.linewidth: 1.5 lines.marker: None lines.markeredgecolor: auto lines.markeredgewidth: 1.0 lines.markerfacecolor: auto lines.markersize: 6.0 lines.scale_dashes: True lines.solid_capstyle: projecting lines.solid_joinstyle: round markers.fillstyle: full mathtext.bf: sans:bold mathtext.cal: cursive mathtext.default: it mathtext.fallback: cm mathtext.fontset: dejavusans mathtext.it: sans:italic mathtext.rm: sans mathtext.sf: sans mathtext.tt: monospace patch.antialiased: True patch.edgecolor: black patch.facecolor: C0 patch.force_edgecolor: False patch.linewidth: 1.0 path.effects: [] path.simplify: True path.simplify_threshold: 0.111111111111 path.sketch: None path.snap: True pcolor.shading: auto pcolormesh.snap: True pdf.compression: 6 pdf.fonttype: 3 pdf.inheritcolor: False pdf.use14corefonts: False pgf.preamble: pgf.rcfonts: True pgf.texsystem: xelatex polaraxes.grid: True ps.distiller.res: 6000 ps.fonttype: 3 ps.papersize: letter ps.useafm: False ps.usedistiller: None savefig.bbox: None savefig.directory: ~ savefig.dpi: figure savefig.edgecolor: auto savefig.facecolor: auto savefig.format: png savefig.orientation: portrait savefig.pad_inches: 0.1 savefig.transparent: False scatter.edgecolors: face scatter.marker: o svg.fonttype: path svg.hashsalt: None svg.image_inline: True text.antialiased: True text.color: black text.hinting: force_autohint text.hinting_factor: 8 text.kerning_factor: 0 text.latex.preamble: text.usetex: False timezone: UTC tk.window_focus: False toolbar: toolbar2 webagg.address: 127.0.0.1 webagg.open_in_browser: True webagg.port: 8988 webagg.port_retries: 50 xaxis.labellocation: center xtick.alignment: center xtick.bottom: True xtick.color: black xtick.direction: out xtick.labelbottom: True xtick.labelcolor: inherit xtick.labelsize: medium xtick.labeltop: False xtick.major.bottom: True xtick.major.pad: 3.5 xtick.major.size: 3.5 xtick.major.top: True xtick.major.width: 0.8 xtick.minor.bottom: True xtick.minor.pad: 3.4 xtick.minor.size: 2.0 xtick.minor.top: True xtick.minor.visible: False xtick.minor.width: 0.6 xtick.top: False yaxis.labellocation: center ytick.alignment: center_baseline ytick.color: black ytick.direction: out ytick.labelcolor: inherit ytick.labelleft: True ytick.labelright: False ytick.labelsize: medium ytick.left: True ytick.major.left: True ytick.major.pad: 3.5 ytick.major.right: True ytick.major.size: 3.5 ytick.major.width: 0.8 ytick.minor.left: True ytick.minor.pad: 3.4 ytick.minor.right: True ytick.minor.size: 2.0 ytick.minor.visible: False ytick.minor.width: 0.6 ytick.right: False