3

I have this general function to invoke a WinForm control:

public static void Invoke(this Control c, Action action)
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action();
}

I'm thinking of making it better by bringing harsher constraints to prevent things that are meaningless, may be like:

button1.Invoke(() => list.Add(1));

Also there can be redundant typing like:

button1.Invoke(() => button1.Hide());

since we are already specifying the this is button1.

So I made it:

public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action(c);
}

Now I'll have to call,

button1.Invoke((c) => c.Hide());

or

button1.Invoke((c) => button1.Hide());

Now I kind of feel that even then there is some more than required typing. If I'm specifying this is button1, then in the lambda expression I do not want to specify a dummy variable c again to tell where to operate on. Is there anyway I can make this shorter again? Perhaps like

button1.Invoke(Hide);

or

button1.Hide.Invoke();

or so in C#?

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • possible duplicate of [Shortest way to write a thread-safe access method to a windows forms control](http://stackoverflow.com/questions/571706/shortest-way-to-write-a-thread-safe-access-method-to-a-windows-forms-control) – nawfal Apr 27 '13 at 00:32
  • Also see http://stackoverflow.com/questions/2367718/automating-the-invokerequired-code-pattern/2367763#2367763 – nawfal Apr 27 '13 at 00:38

5 Answers5

3

To build off other answers, I would put this into a separate extension class.

public static void Invoke<T>(this T c, Action<T> action) where T : Control
    {
        if (c.InvokeRequired)
            c.Invoke(new Action<T, Action<T>>(Invoke), new object[] { c, action });
        else
            action(c);
    }

This will prevent a TargetParameterCountException from being thrown when cross-threading.

To Call:

button1.Invoke(x => x.Hide());
Sparky
  • 51
  • 4
2

First let me say that you may be overthinking this - short code is a great thing, but there's a point where it starts to be confusing for anyone attempting to read the code.

Now, your first suggestion:

button1.Invoke(Hide);

could work, if you make it:

button1.Invoke(button1.Hide); 

because otherwise the compiler cannot know, where to look for the method Hide(). It could even cause some weird behavior, for example if all of this code was in some derived class, like this:

class A : Control {
    public A() {
         Button button1=new Button();
         button1.Invoke(Hide);
    }
}

Now it would compile, but the Hide() method would be the Hide() method of whole control, not the button! The way to achive this is simply:

public static void Invoke(this Control c, Action action) {
    c.Invoke(action);
}

The latter way:

button1.Hide().Invoke();

would work even without adding extension methods, you just need to make it:

((Action)button1.Hide).Invoke();

This of course means the Hide() method gets invoked in the current thread, which is probably not what you want. So make it:

((Action)button1.Hide).Invoke(button1);
public static void Invoke(this Action action, Control c) {
    c.Invoke(action);
}

Sorry for long answer, hope it helps.

cre8or
  • 387
  • 3
  • 10
  • Good answer. I was thinking of something where I need not specify `button1` twice. – nawfal Dec 24 '12 at 10:52
  • Yeah, I don't think that's possible - simply because you need to specify the object for the Invoke() method AND the object for the Hide() method. You may of course derive the Button class and add method like InvokeHide(), but since your goal is to minimize typing, I don't think it's a good idea :-) – cre8or Dec 24 '12 at 10:56
2

You can use SynchronizationContext.Post or SynchronizationContext.Send to have the framework marshal the action to the UI thread, whether it is Windows Forms or WPF. The static SynchronizationContext.Current method will return an appropriate Synchronization context for your application type.

Post executes asynchronously while Send blocks until the action finishes.

The following code will hide a button asynchronously:

SynchronizationContext.Current.Post(_=>button1.Hide(),null);
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
2

I would go with:

public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action(c);
}

and

button.Invoke(c => c.Hide());

It is the one that is the cleanest (you are given the button originally specified back to perform the action on) and safest (you don't have to specify button1 twice...it is given back to you as a parameter to your lambda). I believe this is elegant syntax.

jam40jeff
  • 2,576
  • 16
  • 14
1

It definitely can't be done like button1.Invoke(Hide); or button1.Hide.Invoke(); because of C# syntax restrictions.

But if you're willing to give up IntelliSense, you can make it a bit shorter. As a downside, some bugs which can usually be detected and fixed during compile-time (like typos or mismatch parameters) will become run-time errors. Sometimes it's acceptable, sometimes it's not.

Looking ahead, here is an example usage:

button1.Invoke("Hide");

or

button1.Invoke("ResumeLayout", true);

Solution:

internal static class ExtensionMethods
{
    internal static object Invoke<TControl>(this TControl control,
        string methodName, params object[] parameters)
        where TControl : Control
    {
        object result;

        if (control == null)
            throw new ArgumentNullException("control");

        if (string.IsNullOrEmpty(methodName))
            throw new ArgumentNullException("methodName");

        if (control.InvokeRequired)
            result = control.Invoke(new MethodInvoker(() => Invoke(control,
                methodName, parameters)));
        else
        {
            MethodInfo mi = null;

            if (parameters != null && parameters.Length > 0)
            {
                Type[] types = new Type[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (parameters[i] != null)
                        types[i] = parameters[i].GetType();
                }

                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public,
                    null,  types, null);
            }
            else
                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public);

            if (mi == null)
                throw new InvalidOperationException(methodName);

            result = mi.Invoke(control, parameters);
        }

        return result;
    }
Nick Hill
  • 4,867
  • 3
  • 23
  • 29
  • @nawfal, I agree. But choosing between `((Action)button1.Hide).Invoke();` and `button1.Invoke((c) => c.Hide());` I would choose the last one :) – Nick Hill Dec 24 '12 at 11:17
  • I'll do so because, in particular, for methods with parameters @cre8or's solution will become `((Action)button1.ResumeLayout).Invoke(true);` vs original `button1.Invoke(c => c.ResumeLayout(true));` – Nick Hill Dec 24 '12 at 11:24
  • cre8or's solution will not become `((Action)button1.ResumeLayout).Invoke(true);` since he wasn't recommending it (as pointed out rightly by him that wouldn't avoid cross thread error). He just mentioned it. – nawfal Dec 24 '12 at 11:39
  • @nawfal, so what's his recommended solution? Well, `button1.Invoke(button1.Hide);` will compile after a little fix. But it is still awful: `button1.Invoke((Action)button1.ResumeLayout, true);`, `button1.Invoke((Action)button1.SetBounds, 0, 0, 10, 10);`. And it also breaks type-safety. Although, I agree that mine solution is even less type-safety. – Nick Hill Dec 24 '12 at 12:15
  • He was giving a few options asking me to choose one. I went with my first approach, it isn't big deal. I was just knowing of alternate better answers if any. See the third answer, better. – nawfal Dec 24 '12 at 12:19