2017-08-21 113 views
2

我是測試我在我的應用程序中遇到一個奇怪的錯誤,最後能夠創建一個簡單的再現:ContexMenuStrip特定奇怪的怪異行爲

using System; 
using System.Windows.Forms; 

static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     var notifyIcon1 = new NotifyIcon(); 
     notifyIcon1.Icon = new Form().Icon; 
     notifyIcon1.Visible = true; 
     var contextMenuStrip1 = new ContextMenuStrip(); 
     ToolStripMenuItem menu1 = new ToolStripMenuItem(); 
     menu1.Text = "test"; 
     contextMenuStrip1.Items.Add(menu1); 
     contextMenuStrip1.Items.Add("t1"); 
     contextMenuStrip1.Items.Add("t2"); 

     notifyIcon1.ContextMenuStrip = contextMenuStrip1; 
     var timer = new System.Timers.Timer(); 
     timer.Interval = 3000; 
     timer.Elapsed += (sender, e) => /* Runs in a different thread from UI thread.*/ 
     { 
      if (contextMenuStrip1.InvokeRequired) 
       contextMenuStrip1.Invoke(new Action(() => 
       { 
        menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra"); 
        menu1.DropDownItems.Add(e.SignalTime.ToString()); 
       })); 
      else 
      { 
       menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra"); 
       menu1.DropDownItems.Add(e.SignalTime.ToString()); 
      } 
     }; 
     timer.Start(); 
     Application.Run(); 
    } 
} 

注意上下文菜單中不會公開,但這樣做的任何允許它打開:

  • 刪除每次執行時添加的「額外」下拉項目。 (爲了精確 僅添加0或1每個執行作品)
  • InvokeRequired == false卸下的代碼的一部分(這允許添加每執行多個項目)
  • 卸下t1t2元件。 (它仍然在沒有 根目錄中的其他項目)

這是一個錯誤還是我做錯了什麼?

編輯: 附加到的情況(感謝@derape):

  • 如果移動else分支到單獨的方法,但如果你在InvokeRequired分支機構使用同樣的方法它的工作原理。但是,使用2個方法的名稱和代碼的作用不同。

可能的解決方法可能是在滿月跳舞時穿老虎皮。

+0

嗯,這真的很奇怪。我看到了同樣的行爲。如果我從else分支中提取代碼到它的工作方式中...... – derape

+0

@derape在這裏也是一樣,但如果您在InvokeRequired分支中調用相同的方法,它將不起作用。這真的是bizzare。此外,如果您然後以不同的名稱複製此方法,併爲InvokeRequired分支使用一個副本,並在其他分支中複製它再次工作。 –

回答

1

如果你看看InvokeRequired那麼你會看到有一個明確的檢查IsHandleCreated,它返回false。返回的值並不意味着你不必調用,它只是意味着你不能調用。

讓您更加迷惑:您必須調用,但您還不能。

您可以決定什麼也不做,如果尚未創建手柄(和簡單地錯過項目)或組織單獨的隊列來存儲項目,直到手柄可用,類似於:

var items = new List<string>(); 
timer.Elapsed += (sender, e) => 
{ 
    if (contextMenuStrip1.IsHandleCreated) // always invoke, but check for handle 
     contextMenuStrip1.Invoke(new Action(() => 
     { 

      menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra"); 
      menu1.DropDownItems.Add(e.SignalTime.ToString()); 
      contextMenuStrip1.Refresh(); 
     })); 
    else 
    { 
     lock (items) 
     { 
      items.Add(e.SignalTime.ToString() + "extra"); 
      items.Add(e.SignalTime.ToString()); 
     } 
    } 
}; 
contextMenuStrip1.HandleCreated += (s, e) => 
{ 
    lock (items) 
    { 
     foreach (var item in items) 
      menu1.DropDownItems.Add(item); 
     contextMenuStrip1.Refresh(); 
    } 
    items = null; 
}; 

另注:如果項目被添加到子菜單中,您將需要撥打Refresh,而菜單打開,但子菜單尚未完成,WinForms的奇怪事情。

+0

你寫的東西是有道理的,但爲什麼抽取else分支到單獨的方法允許它運行呢?它是否會以某種方式延遲代碼的執行時間,以便創建句柄? –

+0

@ K.Kowalczyk,我沒有嘗試過,也不關心如果違反規則「在UI線程中訪問UI元素」會發生什麼情況。缺少通知,失敗的事件上升,同步問題等。 – Sinatr