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

質問
-
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 以降
回答
-
(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
- 編集済み kenjinoteMVP 2018年3月19日 9:42
- 回答としてマーク 立花楓Microsoft employee, Moderator 2018年6月12日 5:11
-
(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
- 編集済み VB User1 2018年3月20日 3:34
- 回答の候補に設定 kenjinoteMVP 2018年3月20日 4:19
- 回答としてマーク 立花楓Microsoft employee, Moderator 2018年6月12日 5:11
すべての返信
-
(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 は使わない】ということですよね。
-
(3)後から起動したプロセスのコマンドライン引数を取得する
であれば、Environment.CommandLineで解析前の単一文字列、Environment.GetCommandLineArgsで解析済みの文字列配列をそれぞれ取得できます。 -
(3)に関しては、WMI の Win32_Process クラスを利用することで、取得が可能みたいですね。
【.NET】外部アプリケーションのコマンドライン引数を取得する
https://irislab.org/blog/20151117/appcommandlin/
(追記)
佐祐理様のアドバイスを拝見して、【後から、起動したプロセス(=自分自身)の】の方か、と気づきました。
【後から起動した、(自分以外の)プロセスの】だとばっかり捉えていました。
VB User1 様が想定されているのがどちらか分かりませんが、いろいろありますよ、ということで。。。
- 編集済み sutefu7 2018年3月19日 7:52 説明内容の修正をしたいため
-
質問文に「アプリケーションフレームワークを有効にした場合であれば、上記(2)と(3)は解決の仕方はわかる」とありましたのでConsoleApplicationBase.CommandLineArgsプロパティのことを指されていると判断し、相当する機能を紹介しました。
-
(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
- 編集済み kenjinoteMVP 2018年3月19日 9:42
- 回答としてマーク 立花楓Microsoft employee, Moderator 2018年6月12日 5:11
-
みなさま、コメントいただきありがとうございます。
また、説明が不十分な箇所があり、お詫び申し上げます。
(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さまに「回答としてマーク」をさせていただきますので、引き続きコメントいただけますと幸いです。
-
(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 を使用して実現した場合、多重起動ができなくなるので、後追い追加分の引数を取得できなくなります。
これ以外だと、プロセスに渡したいデータを渡すのではなく、テキストファイル経由などで渡す等しか無いかなと思います。 -
(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
- 編集済み VB User1 2018年3月20日 3:34
- 回答の候補に設定 kenjinoteMVP 2018年3月20日 4:19
- 回答としてマーク 立花楓Microsoft employee, Moderator 2018年6月12日 5:11
-
何度もすみません。
私も再度調べまして、実現できた気がするので以下に返答します。
※あくまでも別解です。提案された対応案良いと思います。
■(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
-
こちらは、(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
-
sutefu7さま
ありがとうございます。
■(2)、(3)の多重起動禁止、かつ引数だけは取得する
を実行してみました。この方法ですと、プロセスBが引数Bを取得していますがプロセスAには渡せていないですよね?
ここで何らかのプロセス間通信が必要になるかと思います。
■(1)、スタートアップ時、フォームを非アクティブで表示する
私の質問が分かり難く申し訳ございません。
アプリケーションフレームワークを有効にしない場合は,ShowWithoutActivation の対処のみで非アクティブ状態でウィンドウが表示する事を確認しております。
しかしながら、CreateParams につきましては大変参考になりました。
誠にありがとうございます。