Automating the InvokeRequired code pattern

后端 未结 9 1178
醉话见心
醉话见心 2020-11-21 23:57

I have become painfully aware of just how often one needs to write the following code pattern in event-driven GUI code, where

private void DoGUISwitch() {
           


        
相关标签:
9条回答
  • 2020-11-22 00:15

    Here's an improved/combined version of Lee's, Oliver's and Stephan's answers.

    public delegate void InvokeIfRequiredDelegate<T>(T obj)
        where T : ISynchronizeInvoke;
    
    public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
        where T : ISynchronizeInvoke
    {
        if (obj.InvokeRequired)
        {
            obj.Invoke(action, new object[] { obj });
        }
        else
        {
            action(obj);
        }
    } 
    

    The template allows for flexible and cast-less code which is much more readable while the dedicated delegate provides efficiency.

    progressBar1.InvokeIfRequired(o => 
    {
        o.Style = ProgressBarStyle.Marquee;
        o.MarqueeAnimationSpeed = 40;
    });
    
    0 讨论(0)
  • 2020-11-22 00:16

    I Kind of like to do it a bit different, i like to call "myself" if needed with an Action,

        private void AddRowToListView(ScannerRow row, bool suspend)
        {
            if (IsFormClosing)
                return;
    
            if (this.InvokeRequired)
            {
                var A = new Action(() => AddRowToListView(row, suspend));
                this.Invoke(A);
                return;
            }
             //as of here the Code is thread-safe
    

    this is a handy pattern, the IsFormClosing is a field that i set to True when I am closing my form as there might be some background threads that are still running...

    0 讨论(0)
  • 2020-11-22 00:18

    Usage:

    control.InvokeIfRequired(c => c.Visible = false);
    
    return control.InvokeIfRequired(c => {
        c.Visible = value
    
        return c.Visible;
    });
    

    Code:

    using System;
    using System.ComponentModel;
    
    namespace Extensions
    {
        public static class SynchronizeInvokeExtensions
        {
            public static void InvokeIfRequired<T>(this T obj, Action<T> action)
                where T : ISynchronizeInvoke
            {
                if (obj.InvokeRequired)
                {
                    obj.Invoke(action, new object[] { obj });
                }
                else
                {
                    action(obj);
                }
            }
    
            public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
                where TIn : ISynchronizeInvoke
            {
                return obj.InvokeRequired
                    ? (TOut)obj.Invoke(func, new object[] { obj })
                    : func(obj);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:18

    I'd rather use a single instance of a method Delegate instead of creating a new instance every time. In my case i used to show progress and (info/error) messages from a Backroundworker copying and casting large data from a sql instance. Everywhile after about 70000 progress and message calls my form stopped working and showing new messages. This didn't occure when i started using a single global instance delegate.

    delegate void ShowMessageCallback(string message);
    
    private void Form1_Load(object sender, EventArgs e)
    {
        ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
    }
    
    private void ShowMessage(string message)
    {
        if (this.InvokeRequired)
            this.Invoke(showMessageDelegate, message);
        else
            labelMessage.Text = message;           
    }
    
    void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
    {
        ShowMessage(e.Message);
    }
    
    0 讨论(0)
  • 2020-11-22 00:19

    Here's the form I've been using in all my code.

    private void DoGUISwitch()
    { 
        Invoke( ( MethodInvoker ) delegate {
            object1.Visible = true;
            object2.Visible = false;
        });
    } 
    

    I've based this on the blog entry here. I have not had this approach fail me, so I see no reason to complicate my code with a check of the InvokeRequired property.

    Hope this helps.

    0 讨论(0)
  • 2020-11-22 00:24

    Lee's approach can be simplified further

    public static void InvokeIfRequired(this Control control, MethodInvoker action)
    {
        // See Update 2 for edits Mike de Klerk suggests to insert here.
    
        if (control.InvokeRequired) {
            control.Invoke(action);
        } else {
            action();
        }
    }
    

    And can be called like this

    richEditControl1.InvokeIfRequired(() =>
    {
        // Do anything you want with the control here
        richEditControl1.RtfText = value;
        RtfHelpers.AddMissingStyles(richEditControl1);
    });
    

    There is no need to pass the control as parameter to the delegate. C# automatically creates a closure.


    UPDATE:

    According to several other posters Control can be generalized as ISynchronizeInvoke:

    public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                             MethodInvoker action)
    {
        if (obj.InvokeRequired) {
            var args = new object[0];
            obj.Invoke(action, args);
        } else {
            action();
        }
    }
    

    DonBoitnott pointed out that unlike Control the ISynchronizeInvoke interface requires an object array for the Invoke method as parameter list for the action.


    UPDATE 2

    Edits suggested by Mike de Klerk (see comment in 1st code snippet for insert point):

    // When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
    // resulting still in a cross-thread exception.
    while (!control.Visible)
    {
        System.Threading.Thread.Sleep(50);
    }
    

    See ToolmakerSteve's comment below for concerns about this suggestion.

    0 讨论(0)
提交回复
热议问题