這是一個很難解釋的情況。我有一個 Windows 表單應用程式,它使用帶有背景關系選單的通知圖示。該應用程式可以配置為不顯示表單,或者用戶可以關閉表單,只留下通知圖示。從通知圖示的背景關系選單條中選擇“顯示”選項將創建(如果關閉)/恢復(如果最小化)主表單。現在這一切正常,因為從選單生成的事件正在 STA 執行緒上運行。問題是單實體實作。通知圖示型別的應用程式真的應該只運行一個實體,否則托盤中有多個通知圖示,這將是一個巨大的混亂。這是使用命名的 EventWaitHandle 完成的,還包括檢測運行第二個實體的嘗試,
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
我遇到的問題是等待來自第二個實體的信號的回圈在另一個執行緒中運行,因此需要委托給 STA 執行緒來恢復/創建表單。我使用 UserControl 來包含 NotifyIcon 和 Menu(所以我可以在設計器中編輯它們),它是在 Program.cs 中創建的,而不是 Application.Run(new Form()) 但這個控制元件實際上從未真正顯示出來,所以它永遠不會獲得分配給它的句柄,我可以呼叫它,所以當第二個實體運行時我得到一個例外。
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize = MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
也許有更好的方法來做到這一點?我確實想過在建構式中運行一個回圈,等待一個鎖定的物件并從另一個執行緒發送它。像這樣
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
但這似乎很混亂。編輯:確實不會作業,因為它會鎖定 STA 執行緒。
uj5u.com熱心網友回復:
我設法像這樣解決它:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize = MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/433993.html
