C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[6]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化


(過去ログ 176 を表示中)

[トピック内 19 記事 (1 - 19 表示)]  << 0 >>

■101173 / inTopicNo.1)  タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
  
□投稿者/ KT (19回)-(2023/01/14(Sat) 00:53:12)

分類:[C#] 

2023/01/14(Sat) 00:55:23 編集(投稿者)

C# Windowsフォームアプリケーション
タスクトレイ常駐アプリを制作しています。

【条件】
@タスクトレイに常駐するアプリ
(×ボタンを押すとフォームがタスクトレイに格納される→タスクトレイのアイコンをクリックするとフォームが再表示される)

Aアプリの多重起動を禁止する

B多重起動を試みる操作があった場合、既に起動しているフォームをアクティブ化する。
(タスクトレイのアイコンをクリックして開くのと同じ動作がしたい)


@はhttps://www.chuken-engineer.com/entry/2019/08/06/200925を参考に実装できました。
Aもhttp://jeanne.wankuma.com/tips/csharp/process/activewindow.htmlを参考に実装できました。
現在、Bの実装に手間取っております。
通常のフォームアプリであれば上記の方法でアクティブ化する事ができるのですが、多重起動を試みた操作があった場合にタスクトレイアプリをアクティブ化する方法がわかりません。
どのような方法で実現できますでしょうか。




引用返信 編集キー/
■101174 / inTopicNo.2)  Re[1]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ 魔界の仮面弁士 (3539回)-(2023/01/16(Mon) 09:39:47)
No101173 (KT さん) に返信
> Aアプリの多重起動を禁止する
> B多重起動を試みる操作があった場合、既に起動しているフォームをアクティブ化する。

多重起動の禁止にはミューテックを使うのが良いでしょう。
https://dobon.net/vb/dotnet/process/checkprevinstance.html
引用返信 編集キー/
■101175 / inTopicNo.3)  Re[1]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ kiku (317回)-(2023/01/16(Mon) 13:03:04)
No101173 (KT さん) に返信
> B多重起動を試みる操作があった場合、既に起動しているフォームをアクティブ化する。
> (タスクトレイのアイコンをクリックして開くのと同じ動作がしたい)

下記が参考になりそうです。
※下記の動作確認はしていません。
https://ez-net.jp/article/01/DbdHjqpD/T7_aZjm_paVi/

引用返信 編集キー/
■101176 / inTopicNo.4)  Re[2]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (20回)-(2023/01/16(Mon) 20:06:19)
■101174 (魔界の仮面弁士 さん) に返信
> 多重起動の禁止にはミューテックを使うのが良いでしょう。
多重起動の禁止まではできました。その後の既に起動しているフォームのアクティブ化ができず悩んでおります。
引用返信 編集キー/
■101177 / inTopicNo.5)  Re[3]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (21回)-(2023/01/16(Mon) 20:08:05)
■101175 (kiku さん) に返信
> 下記が参考になりそうです。。
下記の方法では実現できませんでした。
引用返信 編集キー/
■101178 / inTopicNo.6)  Re[4]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (22回)-(2023/01/16(Mon) 20:12:29)
2023/01/16(Mon) 20:59:46 編集(投稿者)
タスクトレイをクリックしてフォームを開く動作を

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Visible = true;        //フォームの表示
            if (this.WindowState == FormWindowState.Minimized)
            {
                this.WindowState = FormWindowState.Normal;
            }
            this.Activate();
        }

という形で実装しているのですが、これを多重起動を検知した際に呼び出せるような方法はないですかね。

引用返信 編集キー/
■101180 / inTopicNo.7)  Re[5]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ Hongliang (1266回)-(2023/01/16(Mon) 21:18:04)
> という形で実装しているのですが、これを多重起動を検知した際に呼び出せるような方法はないですかね。

色々やり方はありますが、例えばEventWaitHandleで提供される名前付きイベントを使って、以下のようにするとか。
・先行側か後続側かは、Mutexの代わりにEventWaitHandleを作成できたかどうかで判断する
https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.eventwaithandle.-ctor?view=net-7.0#system-threading-eventwaithandle-ctor(system-boolean-system-threading-eventresetmode-system-string-system-boolean@)
・先行側プロセスは(別スレッドで)EventWaitHandleを待機しておく
 ・待機完了したらメインスレッドにフォームの表示を行わせ、再び待機に入る
・後続側プロセスは、EventWaitHandleをSetする
引用返信 編集キー/
■101181 / inTopicNo.8)  Re[3]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ 魔界の仮面弁士 (3540回)-(2023/01/16(Mon) 21:59:17)
No101176 (KT さん) に返信
> 多重起動の禁止まではできました。その後の既に起動しているフォームのアクティブ化ができず悩んでおります。
フォームが非表示なだけで存在はしている状態なら、
対象プロセスのフォームのハンドルに対して、API でアクティブ化してみるとか。
https://dobon.net/vb/dotnet/process/appactivate.html


No101178 (KT さん) に返信
> これを多重起動を検知した際に呼び出せるような方法はないですかね。
別プロセスのメソッドを呼び出したいという話なら、
WCF もしくは .NET Remoting とか。


あるいは、独自の Windowss Message を投げてみるとか。
対象フォームを下記のように実装しておけば、あとから起動した方が
  SendMessage(hWnd, KT_MESSAGE_SHOWANDACTIVE, IntPtr.Zero, IntPtr.Zero);
を投げることで、そのフォームがアクティブ化されます。


private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int RegisterWindowMessage(string lpString);
private readonly int KT_MESSAGE_SHOWANDACTIVE;

public Form1()
{
  InitializeComponent();
  KT_MESSAGE_SHOWANDACTIVE = RegisterWindowMessage("KT_MESSAGE_SHOWANDACTIVE");
}

protected override void WndProc(ref Message m)
{
  base.WndProc(ref m);
  if (m.Msg == KT_MESSAGE_SHOWANDACTIVE)
  {
    ShowAndActive();
  }
}

private void ShowAndActive()
{
  this.Visible = true;
  if(this.WindowState == FormWindowState.Minimized)
  {
    // this.WindowState = FormWindowState.Normal;
    ShowWindowAsync(this.Handle, SW_RESTORE); // 直前の状態は最大化だったかもしれない
  }
  this.Activate();
}
引用返信 編集キー/
■101182 / inTopicNo.9)  Re[6]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (24回)-(2023/01/16(Mon) 23:55:00)
■101180 (Hongliang さん) に返信
なるほど、私の今の技術では理解できそうにないですね…
引用返信 編集キー/
■101183 / inTopicNo.10)  Re[7]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (25回)-(2023/01/16(Mon) 23:55:17)
2023/01/16(Mon) 23:55:37 編集(投稿者)

■101181 (魔界の仮面弁士 さん) に返信
> フォームが非表示なだけで存在はしている状態なら、
> 対象プロセスのフォームのハンドルに対して、API でアクティブ化してみるとか。
こちらの方法は試してみましたが、タスクトレイからアクティブ化する事はできませんでした。
(普通のフォームならできました)

> SendMessage(hWnd, KT_MESSAGE_SHOWANDACTIVE, IntPtr.Zero, IntPtr.Zero);
> を投げることで、そのフォームがアクティブ化されます。
SendMessageというのがよくわからないですが、これはどの部分に記載すればよろしいでしょうか。
多重起動を検知した処理に書けば良いのでしょうか?

引用返信 編集キー/
■101184 / inTopicNo.11)  Re[7]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (26回)-(2023/01/16(Mon) 23:55:42)
2023/01/16(Mon) 23:56:28 編集(投稿者)

■101181 (魔界の仮面弁士 さん) に返信
> フォームが非表示なだけで存在はしている状態なら、
> 対象プロセスのフォームのハンドルに対して、API でアクティブ化してみるとか。
こちらの方法は試してみましたが、タスクトレイからアクティブ化する事はできませんでした。
(普通のフォームならできました)

> SendMessage(hWnd, KT_MESSAGE_SHOWANDACTIVE, IntPtr.Zero, IntPtr.Zero);
> を投げることで、そのフォームがアクティブ化されます。
SendMessageというのがよくわからないですが、これはどの部分に記載すればよろしいでしょうか。
多重起動を検知した処理に書けば良いのでしょうか?

引用返信 編集キー/
■101185 / inTopicNo.12)  Re[7]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (27回)-(2023/01/16(Mon) 23:56:32)
■101181 (魔界の仮面弁士 さん) に返信
> フォームが非表示なだけで存在はしている状態なら、
> 対象プロセスのフォームのハンドルに対して、API でアクティブ化してみるとか。
こちらの方法は試してみましたが、タスクトレイからアクティブ化する事はできませんでした。
(普通のフォームならできました)

> SendMessage(hWnd, KT_MESSAGE_SHOWANDACTIVE, IntPtr.Zero, IntPtr.Zero);
を投げることで、そのフォームがアクティブ化されます。
SendMessageというのがよくわからないですが、これはどの部分に記載すればよろしいでしょうか。
多重起動を検知した処理に書けば良いのでしょうか?

引用返信 編集キー/
■101186 / inTopicNo.13)  Re[8]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (28回)-(2023/01/16(Mon) 23:57:20)
↑編集かけたら連投になってしまいました。
引用返信 編集キー/
■101187 / inTopicNo.14)  Re[8]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ 魔界の仮面弁士 (3541回)-(2023/01/17(Tue) 00:50:03)
No101184 (KT さん) に返信
> こちらの方法は試してみましたが、タスクトレイからアクティブ化する事はできませんでした。
> (普通のフォームならできました)

といっても、単にウィンドウが非表示状態のアプリというだけですよね…?
タスクトレイにはアイコンが登録されるだけであって、振る舞いに影響を与えるものではないような。

そもそも、肝心のウィンドウハンドルはどうやって取得していますか?

Process クラスの MainWindowHandle プロパティは 非表示ウィンドウを捉えられない(Zero を返す)仕様ですから
EnumWindows API などで拾うか、プロセス間通信でハンドルを伝えているのものと推察しますが…。


> SendMessageというのがよくわからないですが、
こういう API です。

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);


> これはどの部分に記載すればよろしいでしょうか。
多重起動を検知した側のプロセスから、既存プロセスのウィンドウハンドルに向けて SendMessage してください。
引用返信 編集キー/
■101188 / inTopicNo.15)  Re[9]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ 魔界の仮面弁士 (3542回)-(2023/01/17(Tue) 02:49:49)
No101187 (魔界の仮面弁士) に追記
> Process クラスの MainWindowHandle プロパティは 非表示ウィンドウを捉えられない(Zero を返す)仕様ですから
> EnumWindows API などで拾うか、プロセス間通信でハンドルを伝えているのものと推察しますが…。

トップレベル ウィンドウのハンドルを得るのであれば、
EnumWindows API よりも、FindWindowEx API の方が良さそうです。

[DllImport("user32", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindowEx(IntPtr parentWnd, IntPtr previousWnd, string className, string windowText);


フォームを非表示にする際に、その非表示フォームの Text に識別用の文字列をセットしておけば、
後から探索する側は、
 IntPtr targetWindow = FindWindowEx(IntPtr.Zero, IntPtr.Zero, null, 識別用の文字列);
で取得できるかと思います。
※識別に使うタイトル文字列は、他と被らない名前を用意する必要があります。


あるいは識別情報を入れておかずに、ウィンドウを全列挙して調べるという事もできますが、
何らかの識別情報が無いと判断し辛いと思います。(アプリには Form 以外の非表示ウィンドウも含まれるので…)

一応、その列挙版も作ってみました。


/// <summary>
/// 自分と同じ名前のプロセスを列挙します。(自分自身は含みません)
/// </summary>
public static TopLevelWindow[] GetOtherProcesses()
{
  var topLevelWindows = new List<TopLevelWindow>();

  var p = Process.GetCurrentProcess();
  var pid = p.Id;
  // 自プロセス以外のプロセス ID を列挙
  var procList = Process.GetProcessesByName(p.ProcessName).Select(x => x.Id).Where(x => x != pid).ToList();

  IntPtr hWnd = IntPtr.Zero;
  while (IntPtr.Zero != (hWnd = FindWindowEx(IntPtr.Zero, hWnd, null, null)))
  {
    var w = new TopLevelWindow(hWnd);
    if (procList.Contains(w.ProcessId))
    {
      topLevelWindows.Add(w);
    }
  }
  return topLevelWindows.ToArray();
}


[DllImport("user32", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindowEx(IntPtr parentWnd, IntPtr previousWnd, string className, string windowText);

public class TopLevelWindow
{
  public IntPtr WindowHandle { get; }
  public string WindowText { get; }
  public int ProcessId { get; }
  public int OwnerThreadId { get; }
  public string ClassName { get; }
  public TopLevelWindow(IntPtr hWnd)
  {
    WindowHandle = hWnd;
    int processId;
    OwnerThreadId = GetWindowThreadProcessId(hWnd, out processId);
    ProcessId = processId;
    var sb = new StringBuilder(257);
    GetClassName(hWnd, sb, sb.Capacity);
    ClassName = sb.ToString();
    sb = new StringBuilder(GetWindowTextLength(hWnd) + 1);
    GetWindowText(hWnd, sb, sb.Capacity);
    WindowText = sb.ToString();
  }
  [DllImport("user32")]
  private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

  [DllImport("user32", CharSet = CharSet.Auto)]
  private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

  [DllImport("user32", CharSet = CharSet.Auto)]
  public static extern int GetWindowTextLength(IntPtr hWnd);

  [DllImport("user32", CharSet = CharSet.Auto)]
  public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
}
引用返信 編集キー/
■101191 / inTopicNo.16)  Re[4]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ 魔界の仮面弁士 (3543回)-(2023/01/17(Tue) 10:34:43)
No101181 (魔界の仮面弁士) に追記
>> これを多重起動を検知した際に呼び出せるような方法はないですかね。
> 別プロセスのメソッドを呼び出したいという話なら、
> WCF もしくは .NET Remoting とか。

WCF で通信してみるサンプル。
参照設定で "System.ServiceModel" を加えておきます。


// ===== ISampleService.cs =====
//
System.ServiceModel;
[ServiceContract]
public interface ISampleService
{
  [OperationContract]
  void ShowAndActive();
}


// ===== Form1.cs (抜粋) =====
//
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public partial class Form1 : Form, ISampleService
{
  // 外部から呼び出したい処理
  // 今回は Form1 自身に ISampleService を直接実装している
  void ISampleService.ShowAndActive()
  {
    this.Visible = true;
    if (this.WindowState == FormWindowState.Minimized)
    {
      const int SW_RESTORE = 9;
      ShowWindowAsync(this.Handle, SW_RESTORE);
    }
    this.Activate();
  }
  [DllImport("user32")]
  private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
}


// ===== Program.cs =====
//
using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

internal static class Program
{
  private static readonly string NetTcpUrl = "net.tcp://localhost:8089";
  private static Mutex mutex;

  [STAThread]
  static void Main()
  {
    mutex = new Mutex(false, "Wankuma101173");
    if (mutex.WaitOne(0, false))
    {
      // まだ起動されていない時の処理

      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      var mainForm = new Form1();

      var netTcpUrl = new Uri(NetTcpUrl);
      var contract = typeof(ISampleService); // ServiceContract 属性付きのインターフェイスを指定
      var host = new ServiceHost(mainForm, netTcpUrl);
      host.AddServiceEndpoint(contract, new NetTcpBinding(), "/nettcp");
      host.Open();

      Application.Run(mainForm);
    }
    else
    {
      // 既に起動されていた時の処理
      // フォームを表示する必要は無いので、Form1 は new しない
      CallNetTcpBinding();
    }
  }

  private static void CallNetTcpBinding()
  {
    var channel = default(IClientChannel);
    var binding = new NetTcpBinding();
    var factory = new ChannelFactory<ISampleService>(binding, new EndpointAddress(NetTcpUrl + "/nettcp"));
    factory.Open();
    try
    {
      var client = factory.CreateChannel();
      channel = (IClientChannel)client;
      channel.Open();

      client.ShowAndActive();   // 既存プロセスの ISampleService.ShowAndActive メソッドを呼び出す

      channel.Close();
    }
    finally
    {
      factory.Close();
    }
  }
}

---

なお、WCF は .NET Framework 向けのものであり、.NET/.NET Core ではサポートされていませんが、
代わりに CoreWCF を使うことができます。(CoreWCF は .NET Framework でも使用できます)

https://www.nuget.org/packages?q=CoreWCF
https://github.com/CoreWCF/CoreWCF/blob/main/Documentation/Microsoft-Support.md
https://github.com/CoreWCF/samples/tree/main/Scenarios/Getting-Started-with-CoreWCF
引用返信 編集キー/
■101192 / inTopicNo.17)  Re[6]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ kiku (318回)-(2023/01/17(Tue) 10:56:55)
No101180 (Hongliang さん) に返信
>>という形で実装しているのですが、これを多重起動を検知した際に呼び出せるような方法はないですかね。
> 
> 色々やり方はありますが、例えばEventWaitHandleで提供される名前付きイベントを使って、以下のようにするとか。
> ・先行側か後続側かは、Mutexの代わりにEventWaitHandleを作成できたかどうかで判断する
> https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.eventwaithandle.-ctor?view=net-7.0#system-threading-eventwaithandle-ctor(system-boolean-system-threading-eventresetmode-system-string-system-boolean@)
> ・先行側プロセスは(別スレッドで)EventWaitHandleを待機しておく
>  ・待機完了したらメインスレッドにフォームの表示を行わせ、再び待機に入る
> ・後続側プロセスは、EventWaitHandleをSetする

上記、Hongliangさんの方法を知らなかったためと、実際に動作させて検証してみたいと思ったため
サンプルを作ってみました。


    public class Global
    {
        public static string _eventWaitHandle_name = "Global\test";
        public static EventWaitHandle _eventWaitHandle;
    }

        static void Main()
        {
            try
            {
                var createdNew = false;
                Global._eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, Global._eventWaitHandle_name, out createdNew);
                if (createdNew == false)
                {
                    //多重起動
                    //親プロセスに多重起動したことを通知
                    Global._eventWaitHandle.Set();
                    //アプリ終了
                    return;
                }

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            catch (Exception)
            {
            }
            finally
            {
                if(Global._eventWaitHandle != null)
                {
                    Global._eventWaitHandle.Dispose();
                    Global._eventWaitHandle = null;
                }
            }
        }

    public partial class Form1 : Form
    {
        bool WaitThreadEndRequest = false;
        public Form1()
        {
            InitializeComponent();
        }
        private async void Form1_Load(object sender, EventArgs e)
        {
            if(Global._eventWaitHandle != null)
            {
                await Task.Run(new Action(thread_wait));
            }
        }
        private void thread_wait()
        {
            try
            {
                while (true)
                {
                    this.Invoke((Action)(() =>
                    {
                        listBox1.Items.Add($"多重起動を待機");
                    }));
                    Global._eventWaitHandle.WaitOne();
                    if (WaitThreadEndRequest)
                    {
                        //待機終了
                        //スレッド終了
                        break;
                    }
                    Global._eventWaitHandle.Reset();
                    //多重起動を検知
                    this.Invoke((Action)(() =>
                    {
                        listBox1.Items.Add($"多重起動を検知");
                    }));
                }
            }
            catch(Exception)
            {
                throw;
            }
            finally
            {
            }
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(Global._eventWaitHandle != null)
            {
                WaitThreadEndRequest = true;
                Global._eventWaitHandle.Set();
                Global._eventWaitHandle.Dispose();
                Global._eventWaitHandle = null;
            }
        }
    }

引用返信 編集キー/
■101194 / inTopicNo.18)  Re[5]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (29回)-(2023/01/17(Tue) 12:18:43)
結論、できました!

■101192 (kiku さん) に返信
こちらのサンプルを使用し、多重起動を検知した部分"this.Invoke((Action)(() =>"にフォームを表示させる処理を入れてやる事で
タスクトレイからアクティブ化する動作が実現できました。
わざわざサンプルまで書いて頂きありがとうございます。

■101187 (魔界の仮面弁士 さん) に返信
こちらの方法も試させて頂きます。
まだちょっと理解しきれていない部分が多いので参考にして勉強させて頂きます。
色々とありがとうございました。
引用返信 編集キー/
■101195 / inTopicNo.19)  Re[6]: タスクトレイ常駐アプリにおいて多重起動を禁止しアクティブ化
□投稿者/ KT (31回)-(2023/01/17(Tue) 12:19:08)
解決しました。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -