Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
269 views
in Technique[技术] by (71.8m points)

c# - How to invoke UI thread in Winform application without a form or control?

I have created a tray application for controlling some hardware components. How can I invoke the UI thread without a main form or control?

The tray app is started with Application.Run(new MyTrayApp()):

class MyTrayApp : ApplicationContext
{
    private NotifyIcon trayIcon;

    public MyTrayApp()
    {            
        trayIcon = new NotifyIcon()
        {
            Icon = Resources.app_icon,
            ContextMenu = new ContextMenu(new MenuItem[] {                
            new MenuItem("Exit", Exit)
        }),
            Visible = true
        };
        // context is still null here
        var context = SynchronizationContext.Current;
        // but I want to invoke UI thread in hardware events
        MyHardWareController controller= new MyHardWareController(context);
    }

    void Exit(object sender, EventArgs e)
    {
        // context is accessible here because this is a UI event
        // too late tho
        var context = SynchronizationContext.Current;
        trayIcon.Visible = false;
        Application.Exit();
    }
}
  1. Control.Invoke() is not available as there are no controls
  2. Searching suggests that SynchronizationContext.Current should be saved for later invoke but there is no ApplicationContext.Load() event...?
  3. I've noticed that MainForm is null in the whole cycle. I wonder how does SynchronizationContext initialized in this case?

Edit:

Just to add some background info on why I would like to invoke UI thread. It is because System.Threading.ThreadStateException will be thrown when attempt to access Windows resources such as Clipboard or SendKeys in another thread:

  HResult=0x80131520
  Message=Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.
  Source=System.Windows.Forms
  StackTrace:
...

It's another can of worms but just for information:

  1. [STAThreadAttribute] is already set for Main function (no effect)
  2. Creating a new STA thread would result in anti-virus deleting my application upon compile

Thus Form.Invoke() or the equivalent to invoke main thread should be the easiest.

Edit 2:

Add a gist for reproducing the error: https://gist.github.com/jki21/eb950df7b88c06cc5c6d46f105335bbf

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Solved it with Application.Idle as mentioned by Loathing! Thanks everyone for your advice!

TrayApp:

class MyTrayApp: ApplicationContext {
  private MyHardwareController controller = null;

  public MyTrayApp() {
    Application.Idle += new EventHandler(this.OnApplicationIdle);
    // ...
  }

  private void OnApplicationIdle(object sender, EventArgs e) {
    // prevent duplicate initialization on each Idle event
    if (controller == null) {
      var context = TaskScheduler.FromCurrentSynchronizationContext();

      controller = new MyHardwareController((f) => {
        Task.Factory.StartNew(
          () => {
            f();
          },
          CancellationToken.None,
          TaskCreationOptions.None,
          context);
      });
    }
  }
  // ...
}

MyHardwareController:

class MyHardwareController {
  private Action < Action > UIInvoke;

  public MyHardwareController(Action < Action > UIInvokeRef) {
    UIInvoke = UIInvokeRef;
  }

  void hardware_Event(object sender, EventArgs e) {
    // Invoke UI thread
    UIInvoke(() => Clipboard.SetText("I am in UI thread!"));
  }

}


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...