我有托盘应用程序从单独的线程上的服务收集数据并用它更新上下文菜单。
UI只能在UI线程上更新,因此我尝试调用UI更新,但是没有窗口就没有创建句柄,因此Invoke将抛出InvalidOperationException,InvokeRequired总是返回false。
那么在这种情况下如何更新我的上下文菜单呢?
编辑: 我创建了这样的托盘图标:
public class TrayIcon : IDisposable
{
NotifyIcon ni;
public TrayIcon()
{
ni = new NotifyIcon();
}
public void Display()
{
ni.Icon = Resources.trayIcon;
ni.Text = "TrayApp";
ni.Visible = true;
ni.ContextMenuStrip = new ContextMenu();
}
public void Dispose()
{
ni.Dispose();
}
}
public static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (TrayIcon icon = new TrayIcon())
{
Settings = new Settings();
icon.Display();
Application.Run();
}
}
EDIT2:数据存储在应用程序中静态可用的Obix工作对象中,事件OnUpdated处理程序在每次更新后执行。
public class ContextMenu: ContextMenuStrip
{
private bool _IsSettingsOpen = false;
private DevicesList DevicesList;
public ContextMenu()
{
DevicesList = new DevicesList();
DevicesList.Text = "Devices";
DevicesList.Image = Resources.online;
Items.Add(DevicesList);
App.Obix.OnUpdated += InvokeUpdate;
}
void InvokeUpdate(object o, EventArgs e)
{
Console.WriteLine(this.IsHandleCreated);//<<false
ContextMenu menu = this;
MethodInvoker method = delegate
{
DevicesList.RePopulate();
};
Invoke(method);//<<InvalidOperationException
}
}
public class DevicesList : ToolStripMenuItem
{
public DevicesList()
{
RePopulate();
}
public void RePopulate()
{
IQueryable<ToolStripItem> listed = (IQueryable<ToolStripItem>)DropDownItems.AsQueryable();
IEnumerable<ToolStripItem> remove = listed.Where(item => !App.Obix.Devices.Contains(new DeviceStatus(item.Name)));
foreach (ToolStripItem rItem in remove)
{
DropDownItems.Remove(rItem);
}
IEnumerable<DeviceStatus> add = App.Obix.Devices.Where(device => DropDownItems.IndexOfKey(device.Name) == -1);
foreach (DeviceStatus aDevice in add)
{
ToolStripMenuItem item = new ToolStripMenuItem();
item.Name = aDevice.Name;
item.Text = aDevice.Name;
DropDownItems.Add(item);
}
foreach (ToolStripMenuItem item in DropDownItems)
{
DeviceStatus device = App.Obix.Devices.Single(_device => _device.Name == item.Name);
if (device.State == "down")
item.Image = Resources.offline;
else
item.Image = Resources.online;
}
}
}
}
答案 0 :(得分:3)
您可以使用InvokeRequired
检查Invoke
和ContextMenuStrip
。它是Control
。
示例强>
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();
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
var timer = new System.Timers.Timer();
timer.Interval = 3000;
timer.Elapsed += (sender, e) => /*Runs in a different thread than UI thread.*/
{
if (contextMenuStrip1.InvokeRequired)
contextMenuStrip1.Invoke(new Action(() =>
{
contextMenuStrip1.Items.Add(e.SignalTime.ToString());
}));
else
contextMenuStrip1.Items.Add(e.SignalTime.ToString());
};
timer.Start();
Application.Run();
}
}
注1: InvokeRequired
属性和Invoke
方法属于Control
类。在上面的代码中,在显示ContextMenuStrip
之前,InvokeRequired
返回false,因为它的句柄尚未创建。但只要您显示ContextMenuStrip
,其InvokeRequired
就会返回true。
注2: InvokeRequired
和Invoke
使用最深的父级。如果控件没有父控件,则将使用控件本身。