none
VB2017 スタートアップフォームがアクティブにならない状態で起動したい。 RRS feed

  • 質問

  • Windowsフォームのアプリケーションを開発しています。

    (1)スタートアップ フォームをアクティブにしない
    (2)単一インスタンス
    (3)後から起動したプロセスのコマンドライン引数を取得する
    (4)P/Invokeは使用しない

    という要件を満たしたプログラムを開発したいのですが、
    どのような方法があるでしょうか?

    アプリケーションフレームワークを有効にした場合であれば、
    上記(2)と(3)は解決の仕方はわかるのですが、
    (1)については以下の方法でもアクティブになってしまいました。

    Protected Overrides ReadOnly Property ShowWithoutActivation() As Boolean
        Get
            Return True
        End Get
    End Property



    Windows 10 Pro
    Visutal Studio Express 2017 for Windows Desktop
    Visual Basic
    .NET Framework 4.5.2 以降


    2018年3月19日 3:59

回答

  • (1) についてですが、確認したところ ShowWithoutActivation プロパティをオーバーライドした場合やウィンドウの拡張スタイルに WS_EX_NOACTIVATE を付けた場合でも、スタートアップフォームの初回表示時は、必ずアクティブになってしまうような挙動でした。(この現象は Visual Basic のみで C# のプロジェクトでは再現しませんでした)

    そこで回避方法ですが、下記のように Shown イベントで Hide → Show とするのはどうでしょうか?(他にも方法はあるかと思いますが…)

    Public Class Form1
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
            Hide()
            Show()
        End Sub
    
        Protected Overrides ReadOnly Property ShowWithoutActivation() As Boolean
            Get
                Return True
            End Get
        End Property
    End Class
    2018年3月19日 9:34
  • (1)の解決策を kenjinoteさま の案を元に考えてみました。

    以下のようにするとアプリケーション実行時にフォーカスが移動することは無いように見えます。

    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
            Me.Hide()
            '非表示状態で起動するときに使用
            'Me.Opacity = 0
        End Sub
    
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
            '非表示状態で起動するときに使用
            'Me.Hide()
        End Sub
    
        Protected Overrides ReadOnly Property ShowWithoutActivation As Boolean
            Get
                Return True
            End Get
        End Property
    
    End Class


    2018年3月20日 3:33

すべての返信

  • (1)に関しては、CreateParams をオーバーライドする対策を試されてみてはいかがでしょうか?
    ※ShowWithoutActivation メソッドと、CreateParams メソッドの組み合わせでの対策

    自分アプリケーションのフォームを非アクティブのまま使う方法が知りたい
    https://social.msdn.microsoft.com/Forums/ja-JP/38b6763a-ece3-4579-9daf-796c2906ca76?forum=vbgeneralja


    (2)、(3)に関しては、【アプリケーションフレームワークを有効にする】以外だと、
    (2)に関しては、Mutex を使う方法が一般的です。
    (3)に関しては、分かりませんでした。

    (4)に関しては、【Windows API は使わない】ということですよね。

    2018年3月19日 6:18
  • (3)後から起動したプロセスのコマンドライン引数を取得する
    であれば、Environment.CommandLineで解析前の単一文字列、Environment.GetCommandLineArgsで解析済みの文字列配列をそれぞれ取得できます。
    2018年3月19日 7:34
  • (3)に関しては、WMI の Win32_Process クラスを利用することで、取得が可能みたいですね。

    【.NET】外部アプリケーションのコマンドライン引数を取得する
    https://irislab.org/blog/20151117/appcommandlin/


    (追記)
    佐祐理様のアドバイスを拝見して、【後から、起動したプロセス(=自分自身)の】の方か、と気づきました。
    【後から起動した、(自分以外の)プロセスの】だとばっかり捉えていました。

    VB User1 様が想定されているのがどちらか分かりませんが、いろいろありますよ、ということで。。。


    • 編集済み sutefu7 2018年3月19日 7:52 説明内容の修正をしたいため
    2018年3月19日 7:34
  • 質問文に「アプリケーションフレームワークを有効にした場合であれば、上記(2)と(3)は解決の仕方はわかる」とありましたのでConsoleApplicationBase.CommandLineArgsプロパティのことを指されていると判断し、相当する機能を紹介しました。
    2018年3月19日 7:59
  • なるほど、確かにその通りですね。
    見ていたのに、見落としていました。

    2018年3月19日 8:52
  • (1) についてですが、確認したところ ShowWithoutActivation プロパティをオーバーライドした場合やウィンドウの拡張スタイルに WS_EX_NOACTIVATE を付けた場合でも、スタートアップフォームの初回表示時は、必ずアクティブになってしまうような挙動でした。(この現象は Visual Basic のみで C# のプロジェクトでは再現しませんでした)

    そこで回避方法ですが、下記のように Shown イベントで Hide → Show とするのはどうでしょうか?(他にも方法はあるかと思いますが…)

    Public Class Form1
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
            Hide()
            Show()
        End Sub
    
        Protected Overrides ReadOnly Property ShowWithoutActivation() As Boolean
            Get
                Return True
            End Get
        End Property
    End Class
    2018年3月19日 9:34
  • みなさま、コメントいただきありがとうございます。
    また、説明が不十分な箇所があり、お詫び申し上げます。

    (3)後から起動したプロセスのコマンドライン引数を取得する

    についてですが、以下の順に実行し

    (A) WindowsApp1.exe 引数A
    (B) WindowsApp1.exe 引数B
    (C) WindowsApp1.exe 引数C

    (A)が引数A、引数B、引数Cを取得したいということです。

    アプリケーションフレームワークを使用した場合は以下のような方法で(A)に引数B、引数Cを渡す事ができます。

    Namespace My
        Partial Friend Class MyApplication
            Private Sub MyApplication_StartupNextInstance(sender As Object, e As StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
                Dim args() As String
                Dim MainForm As Form
                '2つめ以降プロセスの引数を取得
                args = e.CommandLine.ToArray
                'すでに起動済みのプロセスのメインフォームにアクセス
                MainForm = My.Application.MainForm
                'メインフォームに引数を渡す
                'この例は無理やりですが,本来はメソッドやプロパティを用意します。
                MainForm.Text &= String.Join(" ", args) & " "
            End Sub
        End Class
    End Namespace

    また、kenjinoteさまの方法で問題を解決する事ができました。
    誠にありがとうございます。

    しかしながらアプリケーションフレームワークを使わずに(3)を解決する方法があるのか技術的な興味があります。
    後日kenjinoteさまに「回答としてマーク」をさせていただきますので、引き続きコメントいただけますと幸いです。


    2018年3月19日 10:40
  • (2)単一インスタンス という条件を外すことができれば、以下のようなコードで、外部プロセスの引数情報を取得することが可能です。

    Imports System.Management ' 参照設定に追加する必要があります
    
    Public Class Form1
    
        ' より詳しくは、以下を参照
        ' 【.NET】外部アプリケーションのコマンドライン引数を取得する
        ' https://irislab.org/blog/20151117/appcommandlin/
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            ' WMI の Win32_Process クラスを作成
            Using wmiObj As New ManagementClass("Win32_Process")
    
                ' 全てのインスタンス(プロセスの一覧)を取得
                Using items = wmiObj.GetInstances()
    
                    For Each item As ManagementBaseObject In items
                        Console.WriteLine(item("CommandLine"))
                    Next
    
                End Using
    
            End Using
    
        End Sub
    
    End Class
    

    ただし、単一インスタンス対策込みの場合、例えば Mutex を使用して実現した場合、多重起動ができなくなるので、後追い追加分の引数を取得できなくなります。
    これ以外だと、プロセスに渡したいデータを渡すのではなく、テキストファイル経由などで渡す等しか無いかなと思います。
    2018年3月20日 0:20
  • sutefu7さま

    ありがとうございます。
    このようにして他のプロセスの引数を取得することができるのですね。
    大変勉強になります。

    単一インスタンスの場合はタスク間通信などを行うといった工夫が必要になりそうですね。
    2018年3月20日 2:15
  • kenjinoteさま

    ShownイベントでHide()、Show()メソッドを実行する方法で解決したかに見えたのですが、
    エクスプローラーから本プログラムを実行したところ、一度エクスプローラーからフォーカスが離れてまたエクスプローラーに戻るという挙動であることがわかりました。

    やはり、アプリケーションフレームワークを使用する限り致し方ないという事でしょうか。

    ------------------------------------

    ・「事項」を「実行」に訂正


    • 編集済み VB User1 2018年3月20日 3:13
    2018年3月20日 3:06
  • (1)の解決策を kenjinoteさま の案を元に考えてみました。

    以下のようにするとアプリケーション実行時にフォーカスが移動することは無いように見えます。

    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
            Me.Hide()
            '非表示状態で起動するときに使用
            'Me.Opacity = 0
        End Sub
    
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
            '非表示状態で起動するときに使用
            'Me.Hide()
        End Sub
    
        Protected Overrides ReadOnly Property ShowWithoutActivation As Boolean
            Get
                Return True
            End Get
        End Property
    
    End Class


    2018年3月20日 3:33
  • すみません。確かに、Shown イベントで Hide()、Show() と行うとエクスプローラーが一瞬非アクティブになるような動きになりますね。(コード上も表示後にいったん非表示→表示としているので)

    Shown ではなくフォームの Load 時に Hide() を行うことで回避できることをこちらでも確認できました。

    2018年3月20日 4:24
  • 何度もすみません。

    私も再度調べまして、実現できた気がするので以下に返答します。
    ※あくまでも別解です。提案された対応案良いと思います。


    ■(2)、(3)の多重起動禁止、かつ引数だけは取得する

    1.プロジェクトのプロパティより、「アプリケーションフレームワークを有効にする」のチェックを外す
    2.Program.vb とかの名称でモジュール(クラスでも良い)ファイルを追加(以下のソースコード内容)
    3.プロジェクトのプロパティより、「スタートアップオブジェクト」を Program に切り替える

    Imports System.Management
    Imports System.Threading
    
    
    ' C# Windows Formsで同じプロセス名で特定の引数が同じ場合の2重起動防止方法は?
    ' https://ja.stackoverflow.com/questions/41484/c-windows-forms%E3%81%A7%E5%90%8C%E3%81%98%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E5%90%8D%E3%81%A7%E7%89%B9%E5%AE%9A%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%8C%E5%90%8C%E3%81%98%E5%A0%B4%E5%90%88%E3%81%AE2%E9%87%8D%E8%B5%B7%E5%8B%95%E9%98%B2%E6%AD%A2%E6%96%B9%E6%B3%95%E3%81%AF
    
    ' 同名exeならはじきたい
    ' ただし、はじく前に引数は取得しておきたい
    
    
    Module Program
    
        <STAThread()>
        Sub Main(args As String())
    
            ' 多重起動チェック 
            Dim mutexName = "一意を識別できる文字列"
            Dim hasHandle = False
            Dim mutex = New Mutex(False, mutexName)
    
            Try
    
                Try
                    hasHandle = mutex.WaitOne(0, False)
                Catch ex As AbandonedMutexException
                    ' 別のアプリケーションがミューテックスを解放しないで終了した時
                    hasHandle = True
                End Try
    
                If hasHandle Then
                    ' ミューテックスをつかめた最初のexe
    
                    ' (A) WindowsApp1.exe 引数A
                    MessageBox.Show($"最初のexe: {String.Join(", ", args)}")
    
                Else
                    ' 後から起動しようとした、同じexe
    
                    ' 多重起動プロセス
                    ' (B) WindowsApp1.exe 引数B
                    ' (C) WindowsApp1.exe 引数C
                    ArgumentCheck()
    
                    MessageBox.Show("多重起動は禁止です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    Return
                End If
    
                ' 通常起動
                Application.EnableVisualStyles()
                Application.SetCompatibleTextRenderingDefault(False)
                Application.Run(New Form1)
    
            Catch ex As Exception
            Finally
    
                If hasHandle Then
                    mutex.ReleaseMutex()
                End If
                mutex.Close()
    
            End Try
    
    
        End Sub
    
        ' 同一exe、別プロセスの方の引数を取得する
        Sub ArgumentCheck()
    
            Dim thisProcess As Process = Process.GetCurrentProcess()
            Dim thisPid As Integer = thisProcess.Id
            Dim thisExePath As String = thisProcess.MainModule.FileName
    
            ' WMI の Win32_Process クラスを作成
            Using wmiObj As New ManagementClass("Win32_Process")
    
                ' 全てのインスタンス(プロセスの一覧)を取得
                Using items = wmiObj.GetInstances()
    
                    For Each item As ManagementBaseObject In items
    
                        If CStr(item("ExecutablePath")) = thisExePath Then
                            ' 同名exe
    
                            ' 後から起動したexeは、自分自身(と言っても別プロセスの方)と同じプロセスIDを表示することで取得する
                            If CInt(item("ProcessId")) = thisPid Then
    
                                ' 多重起動プロセス
                                ' (B) WindowsApp1.exe 引数B
                                ' (C) WindowsApp1.exe 引数C
                                'Console.WriteLine(item("CommandLine"))
                                MessageBox.Show($"B: 同一exe、多重起動プロセス: {item("CommandLine")}")
    
                            End If
    
                        Else
                            ' 他exe
                        End If
    
                    Next
    
                End Using
    
            End Using
    
        End Sub
    
    End Module
    


    2018年3月20日 6:02
  • こちらは、(1)の方です。

    ■(1)、スタートアップ時、フォームを非アクティブで表示する

    1.プロジェクトのプロパティより、「アプリケーションフレームワークを有効にする」のチェックを外す
    2.Program.vb とかの名称でモジュール(クラスでも良い)ファイルを追加(以下のソースコード内容)
    3.プロジェクトのプロパティより、「スタートアップオブジェクト」を Program に切り替える

    Program.vb

    Module Program
    
        <STAThread()>
        Sub Main(args As String())
    
            Application.EnableVisualStyles()
            Application.SetCompatibleTextRenderingDefault(False)
            Application.Run(New Form1)
    
        End Sub
    
    End Module
    

    Form1.vb

    Public Class Form1
    
        ' 画面上に Label を配置している
        Private Sub Form1_Activated(sender As Object, e As EventArgs) Handles MyBase.Activated
    
            Me.Label1.Text = "Form1_Activated"
    
        End Sub
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
        End Sub
    
        Protected Overrides ReadOnly Property CreateParams As CreateParams
            Get
    
                Dim WS_EX_TOPMOST As Integer = &H8
                Dim WS_EX_NOACTIVATE As Integer = &H8000000
                Dim p As CreateParams = MyBase.CreateParams
                p.ExStyle = p.ExStyle Or WS_EX_TOPMOST Or WS_EX_NOACTIVATE
                Return p
    
            End Get
        End Property
    
        Protected Overrides ReadOnly Property ShowWithoutActivation As Boolean
            Get
                Return True
            End Get
        End Property
    
    End Class
    

    2018年3月20日 6:05
  • sutefu7さま
    ありがとうございます。

    ■(2)、(3)の多重起動禁止、かつ引数だけは取得する
    を実行してみました。この方法ですと、プロセスBが引数Bを取得していますがプロセスAには渡せていないですよね?
    ここで何らかのプロセス間通信が必要になるかと思います。

    ■(1)、スタートアップ時、フォームを非アクティブで表示する
    私の質問が分かり難く申し訳ございません。
    アプリケーションフレームワークを有効にしない場合は,ShowWithoutActivation の対処のみで非アクティブ状態でウィンドウが表示する事を確認しております。
    しかしながら、CreateParams につきましては大変参考になりました。
    誠にありがとうございます。


    2018年3月20日 6:57
  • そうでした。ごめんなさい。
    プロセスAの中で、解決しなければいけないんでしたね。

    了解です。
    こちらも何度も明後日の方向の回答しかできず、申し訳ないです。


    2018年3月20日 8:03