在Tray-only应用程序中多线程和更新UI,

时间:2017-08-21 08:06:51

标签: c# .net multithreading winforms

我有托盘应用程序从单独的线程上的服务收集数据并用它更新上下文菜单。

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;
        }

    }
}

}

1 个答案:

答案 0 :(得分:3)

您可以使用InvokeRequired检查InvokeContextMenuStrip。它是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: InvokeRequiredInvoke使用最深的父级。如果控件没有父控件,则将使用控件本身。