3
\$\begingroup\$

I've written the following small helper class for use in my WPF applications. It comes up from time to time that I need to display message boxes, or otherwise interact with the UI from a non UI thread.

public static class ThreadContext
{
    public static InvokeOnUiThread(Action action)
    {
        if (Application.Current.Dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            Application.Current.Dispatcher.Invoke(action);
        }
    }

    public static BeginInvokeOnUiThread(Action action)
    {
        if (Application.Current.Dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            Application.Current.Dispatcher.BeginInvoke(action);
        }
    }
}

Here is a small example of how this might be used.

public static MyFunction()
{
    ThreadContext.InvokeOnUiThread(
        delegate()
        {
            MessageBox.Show("Hello world!");
        });
}

This works, but declaring the delegate the way I do seems overly verbose.

Is there anyway to make the syntax less verbose while still allowing for arbitrary functions to be passed? Is there anything else that you would suggest to improve this solution -- including an entirely different solution?

\$\endgroup\$
4
\$\begingroup\$

anonymous methods to the rescue! I saw somebody griping about this syntax recently on a blog and found it unfortunate they were dissimenating this. The cleaner new and happy way to define a delegate, is quite simply:

The method signature without a name or types, so for example:

Sum(int a, int b)

becomes:

(a, b)

Follow this with our trusty lambda operator => and then our method body! This may be in a statement block between our trusty {} or just in a single line the same way you can put a single line after an if or while etc.

So in conclusion there are 3 ways to create a method:

Standard (must be declared as class member level):

public int Sum(int a, int b)
{
  return a+b;
}

Delegate (can be declared and instantiated as a method local):

delegate(int a, int b)
{
  return a+b; // god help me if this syntax is correct, I haven't created a delegate in years
}

Anonymous method (can be declared and instantiated as a method local):

(a, b) => { return a+b; };

Back to your example...

ThreadContext.InvokeOnUiThread(
    delegate()
    {
        MessageBox.Show("Hello world!");
    });

In anonymous method format becomes:

ThreadContext.InvokeOnUiThread(() => { MessageBox.Show("Hello world!"); });

Or if you prefer:

Action actionToInvokeOnUiThread = () => { MessageBox.Show("Hello world!"); };
ThreadContext.InvokeOnUiThread(actionToInvokeOnUiThread);
\$\endgroup\$
  • \$\begingroup\$ Nice breakdown. +1 \$\endgroup\$ – jlnorsworthy Aug 26 '11 at 3:30
2
\$\begingroup\$

To improve upon @Johan Larsson's solution, you can implement a class for showing dialogs globally in a core library without the need to define the actual dialog control in the same library. The big difference (and gain) here is @Johan Larsson's solution assumes both are defined in the same assembly (not always possible), whereas the following does not.

In core library (references whatever):

/// <summary>
/// Provides facilities for showing a dialog anywhere.
/// </summary>
public static class Dialog
{
    /// <summary>
    /// Shows a dialog with an error message.
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    public static void ShowError(string title, string message)
    {
        //If application implements the right interface and the instance returned is not null, call the appropriate method
        (Application.Current as IDialogHost)?.GetDialog()?.ShowError(title, message);
    }
}

/// <summary>
/// Specifies a dialog.
/// </summary>
public interface IDialog
{
    void ShowError(string title, string text);
}

/// <summary>
/// Specifies an element capable of hosting dialogs.
/// </summary>
public interface IDialogHost
{
    /// <summary>
    /// Gets a new instance of <see cref="IDialog"/>.
    /// </summary>
    /// <returns></returns>
    IDialog GetDialog();
}

In control library (references core library and whatever):

public class DialogControl : Window, IDialog
{
    public void ShowError(string title, string message)
    {
        Content = message;
        Title = title;
        ShowDialog();
    }

    public DialogControl() : base()
    {
    }
}

In assembly of current application (references core and control library and whatever):

public class MyApp : Application, IDialogHost
{
    /// <summary>
    /// Initializes a new instance of the dialog type the current application supports.
    /// </summary>
    /// <returns></returns>
    IDialog IDialogHost.GetDialog()
    {
        return new DialogControl();
    }
}

The reason GetDialog is implemented explicitly is to encourage the developer to use Dialog class for calls instead; otherwise, anytime you have a reference to the current application, you'd be able to get a new instance of a dialog (which serves no clear purpose on it's own).

Now, any assembly that references the core library can show a dialog, assuming the current application that references it implements IDialogHost. In addition, each application can specify a different dialog control, though, realistically, you'll probably only ever use one.

\$\endgroup\$
1
\$\begingroup\$

I usually implement it like this:

public class Dialogs : IDialogs
{
    public static void ShowError(string title, string message)
    {
        Application.Current.Dispatcher.Invoke(() => ShowErrorDialog(title, message));
    }

    void IDialogs.ShowError(string title, string message)
    {
        ShowError(title, message);
    }

    private static void ShowErrorDialog(string title, string message)
    {
        // pseudo code here
        var dialog = new ErrorDialog { Title = title, DataContext = message };
        dialog.ShowDialog();
    }
}

public interface IDialogs
{
    void ShowError(string title, string text);
}

This allows for:

  • Showing dialogs from the model as IDialogs can be defined anywhere without adding references to any WPF-assemblies.
  • Mocking IDialogs in tests.
  • Changing how error messages are rendered in one place.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.