System.Windows.Threading.Dispatcher and WinForms?
Asked Answered
I

8

36

Does a System.Windows.Threading.Dispatcher work on the UI-thread of a WinForms application?

If yes, why? It is coming from WindowsBase.dll which seems to be a WPF component.

If not, how can I invoke work units back onto the UI-thread? I've found Control.BeginInvoke(), but it seems clumsy to create a control only to reference the originating thread.

Inhabited answered 19/11, 2008 at 19:49 Comment(0)
O
28

You can use Dispatcher even in a WinForms app.

If you are sure to be on a UI thread (e.g. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can later use to dispatch from background threads to the UI thread as usual.

Oulman answered 21/7, 2009 at 15:42 Comment(0)
T
16

Dispatcher is a WPF component, not a WinForms component.

If you want to dispatch work items on the UI thread, then you would have to either use Control.BeginInvoke as you've already found, or react to ResetEvents/WaitObjects across threads.

Usually invoking work items on the UI thread is a bad thing unless it's a UI piece of work (ie. updating a control's content or something) in which case the Control.BeginInvoke() would be sufficient.

Thorma answered 19/11, 2008 at 20:17 Comment(1)
Is that a yes or a no? And how does WinForms interop play into this?Inhabited
A
12

I provided an example of using System.Windows.Threading.Dispatcher in Windows Form in my answer to question "Parallel Programming using TPL on WinForms" since the previous answer to your question:

If you are sure to be in UI thread (eg. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can use to dispatch from background thread to UI thread as usual.

is either misleading or confusing or lacks the concrete usage context:

  • button.Click handler does not assure to be on UI thread;
  • if you are not on UI thread, it is still possible to use the disparcher of UI thread of WinForms form

One can get dispatcher of WinForm UI thread:

Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;

in either button click event handler or anywhere else (in form constructor)

And then use it to execute on UI from other threads, see more details on example below in my answer:

private void button1_Click(object sender, EventArgs e)
{
  Dispatcher dispUI = Dispatcher.CurrentDispatcher;
  for (int i = 2; i < 20; i++)
  {
    int j = i;
    var t = Task.Factory.StartNew
           (() =>
      {
        var result = SumRootN(j);
        dispUI.BeginInvoke
            (new Action
                 (() => richTextBox1.Text += "root " + j.ToString()
                       + " " + result.ToString() + Environment.NewLine
                 )
             , null
            );
      }
           );
}
Acknowledgment answered 15/4, 2013 at 4:30 Comment(2)
using System.Threading; "Type or namespace "Dispatcher" Could not be found...."Ayana
You state "button.Click handler does not assure to be on UI thread" then "One can get dispatcher of WinForm UI thread [...] in either button click event handler ". How is that different? How can we ensure we are in the UI thread, short of getting the dispatcher in the form constructor?Hurling
J
1

Use background worker thread as it's UI message pump aware, This MSDN Article though mostly about WPF does state that the BWT is UI aware even for windows forms.

Judsen answered 19/11, 2008 at 20:31 Comment(0)
S
1

I had similar problem using Oracle dependency class which runs on its own thread within Winforms,

When OnChange event fired from Oracle Dependency, I wanted to show the changes in DataGridView by simply setting DataSource to eventargs.Details (which is essentially a DataTable), and it throws: System.InvalidOperationException was unhandled by user code Message=Cross-thread operation not valid: Control 'dataGridView1' accessed from a thread other than the thread it was created on.

StackOverflow user Brian Peiris ([email protected]) ,collegue of mine showed me this way around:

void dep_OnChange(object sender, OracleNotificationEventArgs arg)
         {
         Console.WriteLine("Notification received");

         int infoSum = int.Parse(arg.Details.Compute("Sum(Info)", "Info is not null").ToString());
         InfoSum x = (InfoSum)infoSum;
         foreach (DataRow dr in arg.Details.Rows)
            {
            Console.WriteLine(string.Format("Operation(InfoSum)= {0}", Enum.GetName(typeof(InfoSum), x)));
            Console.WriteLine(string.Format("ontable={0}  Rowid={1},info={2}", dr.Field<string>("ResourceName"), dr.Field<string>("rowid"), dr.Field<Int32>("info")));
            }
         // Following  will throw cross-thread 
         // dataGridView1.DataSource = arg.Details;
         // instead of line above use the following
         dataGridView1.BeginInvoke((Action)(()=>dataGridView1.DataSource = arg.Details));
         IsNotified = true;
         }

      }
Shifra answered 27/9, 2012 at 20:29 Comment(0)
T
1

I use ViewModels with databinding directly to properies in the ViewModel itself. In some cases, the properties are updated on a different thread. To avoid crashing I make use of the Dispatcher. When the ViewModel is instantiated it captures the current dispatcher and can then use it later as needed.

The one assumption I make is that the ViewModel itself is created on the main thread, and this is easily guaranteed as my ViewModels are always created in the constructor of the associated view (Form/Control), which always runs on the UI thread by design.

I created helper methods to set a property value. This helper calls RaisePropertyChanged. I made a "thread safe" override that can be used to ensure the raised event is fired on the main thread. When done this way, the UI component that is bound to the property will update itself on the UI thread even though the propery was updated on a different thread.

So for me it looks something like this:

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private Dispatcher _dispatcher;

    public ViewModelBase()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }

    protected void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        field = value;
        RaisePropertyChanged(propertyName);

        return true;
    }

    protected bool SetFieldOnMainThread<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        field = value;
        RunOnUiThread(() => RaisePropertyChanged(propertyName));

        return true;
    }

    protected void RunOnUiThread(Action action)
    {
        if (action != null)
        {
            _dispatcher.Invoke(action);
        }
    }
}


// Used like this:
public class TestViewModel : ViewModelBase
{
    private string _name;
    public string Name {
        get => _name;
        set => SetFieldOnMainThread(ref _name, value);
    }
}
Tonie answered 11/3, 2022 at 23:4 Comment(0)
T
0

Take a look at backgrounder and see if it fits your needs.

Twinned answered 14/10, 2009 at 23:15 Comment(1)
A) backgrounder is GPL. Therefore, it's only useful for projects that are also GPL. B) If you're using .NET 4 or later, this functionality is built into the API. See System.Threading.Tasks.Task.Silicious
O
0

Sometimes a Timer component is useful and easy to setup in WinForms, just set its interval and then enable it, then make sure the first thing you do in its Tick event handler is to disable itself.

I think Timer runs the code in its own thread, so you may still need to do a BeginInvoke (called upon the WinForm object [this]) to run your Action.

private WebBrowserDocumentCompletedEventHandler handler; //need to make it a class field for the handler below (anonymous delegates seem to capture state at point of definition, so they can't capture their own reference)
private string imageFilename;
private bool exit;

public void CaptureScreenshot(Uri address = null, string imageFilename = null, int msecDelay = 0, bool exit = false)
{
  handler = (s, e) =>
   {
     webBrowser.DocumentCompleted -= handler; //must do first

     this.imageFilename = imageFilename;
     this.exit = exit;

     timerScreenshot.Interval = (msecDelay > 0)? msecDelay : 1;
     timerScreenshot.Enabled = true;
   };

  webBrowser.DocumentCompleted += handler;
  Go(address); //if address == null, will use URL from UI
}

private void timerScreenshot_Tick(object sender, EventArgs e)
{
  timerScreenshot.Enabled = false; //must do first

  BeginInvoke((Action)(() => //Invoke at UI thread
  { //run in UI thread

    BringToFront();
    Bitmap bitmap = webBrowser.GetScreenshot();

    if (imageFilename == null)
      imageFilename = bitmap.ShowSaveFileDialog();

    if (imageFilename != null)
    {
      Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(imageFilename))); //create any parent directories needed
      bitmap.Save(imageFilename);
    }

    bitmap.Dispose(); //release bitmap resources

    if (exit)
      Close(); //this should close the app, since this is the main form

  }), null);
}

you can see the above in action at WebCapture tool (http://gallery.clipflair.net/WebCapture, source code at: http://ClipFlair.codeplex.com, see Tools/WebCapture folder) that grabs screenshots from websites. BTW, if you want to call the executable from command-line make sure you go to Properties of the project and at Security tab turn-off ClickOnce security (else it can't access command-line)

Orts answered 25/8, 2013 at 15:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.