2017-08-21 2 views
2

Ich habe Tray-only-Anwendung sammelt Daten vom Dienst in separaten Thread und aktualisieren Kontextmenü damit.Multithreading und Aktualisierung der Benutzeroberfläche in Nur-Schale-Anwendung,

UI kann nur auf UI-Thread aktualisiert werden, also versuche ich UI update aufzurufen, aber ohne Fenster wird kein Handle erstellt, sodass Invoke InvalidOperationException auslöst und InvokeRequired immer false zurückgibt.

Also, wie kann ich mein Kontextmenü in diesem Fall aktualisieren?

EDIT: Ich eröffne mein Tray-Icon wie folgt aus:

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: Daten innerhalb oBIX Arbeiter Objekt gespeichert ist verfügbar in Anwendung dabei statisch, Event-Handler OnUpdated nach jedem Update auszuführen.

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

Zeigen Sie den Code an, der die Benutzeroberfläche aktualisiert. – CodeCaster

+0

Ihr Code verfügt nicht über das von Ihnen erwähnte "Invoke" oder "InvokeRequired". – mjwills

+0

@CodeCaster Bearbeitete meine Frage –

Antwort

3

können Sie überprüfen InvokeRequired und Invoke Ihre ContextMenuStrip verwenden. Es ist ein Control.

Beispiel

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

Anmerkung 1:InvokeRequired Eigenschaft und und Invoke Methode gehören zu Control Klasse. Im obigen Code, vor dem Anzeigen der ContextMenuStrip seine InvokeRequired gibt false zurück, weil sein Handle nicht erstellt wurde. Aber sobald Sie das ContextMenuStrip zeigen, wird sein InvokeRequired True zurückgeben.

Hinweis 2:InvokeRequired und Invoke Verwenden Sie den tiefsten verfügbaren Elternteil. Für den Fall, dass das Steuerelement kein Elternteil hat, wird das Steuerelement selbst verwendet.

+0

Problem mit diesem ist, dass InvokeRequired ohne Fenster nie wahr sein wird. und Änderungen werden außerhalb des Benutzeroberflächen-Threads vorgenommen. –

+0

Sie können es einfach testen. Führen Sie die Anwendung aus und klicken Sie dann mit der rechten Maustaste auf das Benachrichtigungssymbol. Es ist nur ein einfaches Beispiel, aber beweist die Lösung. –

+1

Es stellte sich heraus, es funktioniert, und Grund, warum mein Programm nicht funktioniert, liegt woanders. Vielen Dank. –

Verwandte Themen