feat: 实现单实例应用程序支持
This commit is contained in:
17
MainForm.cs
17
MainForm.cs
@@ -572,6 +572,23 @@ namespace TimerApp
|
|||||||
this.Activate();
|
this.Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ActivateFromExternal()
|
||||||
|
{
|
||||||
|
if (InvokeRequired)
|
||||||
|
{
|
||||||
|
BeginInvoke(new Action(ActivateFromExternal));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Show();
|
||||||
|
this.WindowState = FormWindowState.Normal;
|
||||||
|
this.ShowInTaskbar = true;
|
||||||
|
this.BringToFront();
|
||||||
|
this.Activate();
|
||||||
|
this.TopMost = true;
|
||||||
|
this.TopMost = false;
|
||||||
|
}
|
||||||
|
|
||||||
private void toolStripMenuItemExit_Click(object sender, EventArgs e)
|
private void toolStripMenuItemExit_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_monitor.Stop();
|
_monitor.Stop();
|
||||||
|
|||||||
22
Program.cs
22
Program.cs
@@ -8,11 +8,21 @@ static class Program
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
TaskbarIntegration.InitializeProcess();
|
if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null)
|
||||||
// To customize application configuration such as set high DPI settings or default font,
|
{
|
||||||
// see https://aka.ms/applicationconfiguration.
|
SingleInstanceManager.SignalExistingInstance();
|
||||||
ApplicationConfiguration.Initialize();
|
return;
|
||||||
TaskbarIntegration.InitializeShortcuts();
|
}
|
||||||
Application.Run(new MainForm());
|
|
||||||
|
using (instance)
|
||||||
|
{
|
||||||
|
TaskbarIntegration.InitializeProcess();
|
||||||
|
ApplicationConfiguration.Initialize();
|
||||||
|
TaskbarIntegration.InitializeShortcuts();
|
||||||
|
|
||||||
|
var mainForm = new MainForm();
|
||||||
|
instance.StartServer(mainForm.ActivateFromExternal);
|
||||||
|
Application.Run(mainForm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
SingleInstanceManager.cs
Normal file
118
SingleInstanceManager.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TimerApp
|
||||||
|
{
|
||||||
|
internal sealed class SingleInstanceManager : IDisposable
|
||||||
|
{
|
||||||
|
private const string MutexName = "Local\\TimerApp.SingleInstance";
|
||||||
|
private const string PipeName = "TimerApp.SingleInstancePipe";
|
||||||
|
|
||||||
|
private readonly Mutex _mutex;
|
||||||
|
private readonly CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
|
private SingleInstanceManager(Mutex mutex)
|
||||||
|
{
|
||||||
|
_mutex = mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryAcquire(out SingleInstanceManager? manager)
|
||||||
|
{
|
||||||
|
manager = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mutex = new Mutex(true, MutexName, out bool createdNew);
|
||||||
|
if (!createdNew)
|
||||||
|
{
|
||||||
|
mutex.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
manager = new SingleInstanceManager(mutex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SignalExistingInstance()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out);
|
||||||
|
client.Connect(200);
|
||||||
|
using var writer = new StreamWriter(client) { AutoFlush = true };
|
||||||
|
writer.WriteLine("show");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartServer(Action onShowRequested)
|
||||||
|
{
|
||||||
|
Task.Run(() => ServerLoop(onShowRequested, _cts.Token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task ServerLoop(Action onShowRequested, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var server = new NamedPipeServerStream(
|
||||||
|
PipeName,
|
||||||
|
PipeDirection.In,
|
||||||
|
1,
|
||||||
|
PipeTransmissionMode.Byte,
|
||||||
|
PipeOptions.Asynchronous
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
using var reader = new StreamReader(server);
|
||||||
|
string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (string.Equals(line, "show", StringComparison.OrdinalIgnoreCase))
|
||||||
|
onShowRequested();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await Task.Delay(150, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mutex.ReleaseMutex();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
_mutex.Dispose();
|
||||||
|
_cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user