PySide2 (Qt fot Python) QtSingleApplicationでスタンドアロンアプリケーションの多重起動を防ぐ

f:id:ryunnnu:20190724132032p:plain

アプリケーションの多重起動を防ぐ

同じウィンドウが複数立ち上がって欲しくないスタンドアロンのアプリケーションを開発していたので、何か良い方法は無いかと探してみました。

MayaでWindowを作成するときにglobalに逃がす方法をよく使いますが、 似たような方法が出来ないかと探していたところ、Qt(C++)にQtSingleApplicationなるものがあると分かりました。

細かい説明は良いから実行したい という方は飛ばしてください。完成したコードはこちら

目次

QtSingleApplication とは

  • QtSingleApplication Class Reference

    • 原文
      The QtSingleApplication class provides an API to detect and communicate with running instances of an application.
      This class allows you to create applications that cannot have multiple instances running on the same machine for the same user.

    • Google翻訳
      QtSingleApplicationクラスは、アプリケーションの実行中のインスタンスを検出して通信するためのAPIを提供します。
      このクラスを使用すると、同じユーザーに対して同じマシン上で複数のインスタンスを実行できないアプリケーションを作成できます。

    QtSingleApplicationQtの公式な機能ではなくQtSolutionsという追加機能として実装されているものらしいです。

    ぶっちゃけ詳しくはよく分からない...

    github.com

PySide2.QtSingleApplication

QtSolutionsPySide2には組み込まれていない

なので自前で用意する必要があるのですが、QtSingleApplicationPythonで構築したものが公開されていたので有り難く使わせて頂く事にしました。

stackoverflow.com

同じidのウィンドウが app .isRunning() == True 動いていた場合、処理を終了するってことですね。(ざっくり

app = QtSingleApplication(appGuid, sys.argv)
if app.isRunning():
    sys.exit(0)

QtSingleApplication.activateWindow 実行時に最小化を解除してraiseする。

同じウィンドウが app .isRunning() == False 動いていなかった場合は QtSingleApplicationが継承したQApplicationを利用してwindowを立ち上げます。

w = QWidget()
w.show()
app.setActivationWindow(w)
def activateWindow(self):
    if not self._activationWindow:
        return
    self._activationWindow.setWindowState(self._activationWindow.windowState() & ~Qt.WindowMinimized)
    self._activationWindow.raise_()
    self._activationWindow.activateWindow()
  • QWidget.setWindowState

    self._activationWindow.setWindowState(self._activationWindow.windowState() & ~Qt.WindowMinimized)

    windowStateを確認してWindowMinimizedを解除しているらしい

  • QWidget.raise_

    self._activationWindow.raise_()の部分でwindowを一番手前に表示するのですが、どうやらこのraise_()は同じQApplication間のみで動作しないらしく別のアプリケーションより手前には来てくれません。

    PySide.QtGui.QWidget.raise_()

    • 原文
      Raises this widget to the top of the parent widget’s stack.

    • Google翻訳
      このウィジェットを親ウィジェットのスタックの一番上に上げます。

QWidget.raise_()で最前面に表示できないのでwin32gui.SetWindowPosを使う

win32API を利用して 常に最前面に表示する & 常に最前面に表示するを解除する を実行して疑似的にウィンドウを最前面に表示させます。

SetWindowPos(self._activationWindow.winId(),
                win32con.HWND_TOPMOST, # = always on top. only reliable way to bring it to the front on windows
                0, 0, 0, 0,
                win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW)

SetWindowPos(self._activationWindow.winId(),
                win32con.HWND_NOTOPMOST, # disable the always on top, but leave window at its top position
                0, 0, 0, 0,
                win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW)
  • 原文
    always on top. only reliable way to bring it to the front on windows
    disable the always on top, but leave window at its top position

  • Google翻訳
    常にトップに。 窓の前にそれを持って来る唯一の信頼できる方法
    常に手前に表示するのを無効にして、ウィンドウを一番上の位置にします。

この処理をQSingleApplication.activateWindowに組み込めば完成です。

stackoverflow.com

出来たやつ

f:id:ryunnnu:20190729135403g:plain github.com

これでスタンドアロンアプリケーションの多重起動を防ぐ処理が完了しました。 追加機能として最小化を解除し、最前面に表示することも可能になりました。

メモ

PySideの機能にも最前面に表示するフラグを使用することは可能です。

しかしウィンドウを再度表示しなければならず、一瞬ウィンドウが消えて挙動がおかしいように感じさせてしまいそうだったので今回のwin32APIを利用する手法を取りました。