Results 1 to 5 of 5

Thread: [Extension] Set control properties across threads

Threaded View

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    [Extension] Set control properties across threads

    [VB version here]


    Updated: includes methods and functions now as well.


    Update:
    ForumAccount suggested that the code can be used for any type that implements ISynchronizeInvoke, not just Controls, see here. Since I don't have time to update all this code right now, if you need this change you can just change every Control reference to ISynchronizeInvoke instead.


    -------

    Hi,

    Usually if you want to set a property of a control from another thread you will get an invalid cross-thread operation exception. To solve this you need to invoke a delegate on the same thread, which usually goes like this:
    csharp Code:
    1. using System;
    2. using System.Linq;
    3. using System.Linq.Expressions;
    4. using System.Reflection;
    5. using System.Windows.Forms;
    6.  
    7. namespace ThreadSafeExtensions
    8. {
    9.     public static class Extensions
    10.     {
    11.         private delegate void SetThreadSafePropertyDelegate<T>(
    12.             Control control, Expression<Func<T>> property, T value);
    13.         private delegate T GetThreadSafePropertyDelegate<T>(Control control, Expression<Func<T>> property);
    14.         private delegate void InvokeThreadSafeMethodDelegate(Control control, Expression<Action> method);
    15.         private delegate T InvokeThreadSafeFunctionDelegate<T>(Control control, Expression<Func<T>> function);
    16.  
    17.  
    18.         /// <summary>
    19.         /// Sets the specified property of this control to the specified value safely across threads by invoking a delegate if necessary.
    20.         /// </summary>
    21.         /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    22.         /// <param name="control">The control to set the property on.</param>
    23.         /// <param name="property">The property to set as a lambda expression.</param>
    24.         /// <param name="value">The new value of the property.</param>
    25.         public static void SetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property, T value)
    26.         {
    27.             if (control.InvokeRequired)
    28.             {
    29.                 var del = new SetThreadSafePropertyDelegate<T>(SetThreadSafeProperty);
    30.                 control.Invoke(del, control, property, value);
    31.             }
    32.             else
    33.             {
    34.                 PropertyInfo propertyInfo = GetPropertyInfo(property);
    35.                 if (propertyInfo != null)
    36.                 {
    37.                     propertyInfo.SetValue(control, value, null);
    38.                 }
    39.             }
    40.         }
    41.  
    42.         /// <summary>
    43.         /// Gets the value of the specified property of this control safely across threads by invoking a delegate if necessary.
    44.         /// </summary>
    45.         /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    46.         /// <param name="control">The control to get the property from.</param>
    47.         /// <param name="property">The property to get the value from as a lambda expression.</param>
    48.         /// <returns>The value of the specified property.</returns>
    49.         public static T GetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property)
    50.         {
    51.             if (control.InvokeRequired)
    52.             {
    53.                 var del = new GetThreadSafePropertyDelegate<T>(GetThreadSafeProperty);
    54.                 return (T) control.Invoke(del, control, property);
    55.             }
    56.             else
    57.             {
    58.                 PropertyInfo propertyInfo = GetPropertyInfo(property);
    59.                 if (propertyInfo != null)
    60.                 {
    61.                     return (T) propertyInfo.GetValue(control, null);
    62.                 }
    63.             }
    64.             return default(T);
    65.         }
    66.  
    67.         /// <summary>
    68.         /// Invokes a method of this control safely across threads by invoking a delegate if necessary.
    69.         /// </summary>
    70.         /// <param name="control">The control to invoke the method on.</param>
    71.         /// <param name="method">The method to invoke as an expression.</param>
    72.         public static void InvokeThreadSafeMethod(this Control control, Expression<Action> method)
    73.         {
    74.             if (control.InvokeRequired)
    75.             {
    76.                 var del = new InvokeThreadSafeMethodDelegate(InvokeThreadSafeMethod);
    77.                 control.Invoke(del, control, method);
    78.             }
    79.             else
    80.             {
    81.                 method.Compile().DynamicInvoke();
    82.             }
    83.         }
    84.  
    85.         /// <summary>
    86.         /// Invokes a function of this control safely across threads by invoking a delegate if necessary.
    87.         /// </summary>
    88.         /// <param name="control">The control to invoke the function on.</param>
    89.         /// <param name="function">The function to invoke as an expression</param>
    90.         public static T InvokeThreadSafeFunction<T>(this Control control, Expression<Func<T>> function)
    91.         {
    92.             if (control.InvokeRequired)
    93.             {
    94.                 var del = new InvokeThreadSafeFunctionDelegate<T>(InvokeThreadSafeFunction);
    95.                 return (T) control.Invoke(del, control, function);
    96.             }
    97.             else
    98.             {
    99.                 return (T) function.Compile().DynamicInvoke();
    100.             }
    101.         }
    102.        
    103.         private static MemberInfo GetMemberInfo(Expression expression)
    104.         {
    105.             MemberExpression memberExpression;
    106.             LambdaExpression lambda = (LambdaExpression)expression;
    107.             if (lambda.Body is UnaryExpression)
    108.             {
    109.                 UnaryExpression unaryExpression = (UnaryExpression)lambda.Body;
    110.                 memberExpression = (MemberExpression)unaryExpression.Operand;
    111.             }
    112.             else
    113.             {
    114.                 memberExpression = (MemberExpression)lambda.Body;
    115.             }
    116.  
    117.             return memberExpression.Member;
    118.         }
    119.  
    120.         private static PropertyInfo GetPropertyInfo(Expression expression)
    121.         {
    122.             MemberInfo memberInfo = GetMemberInfo(expression);
    123.             if (memberInfo.MemberType == MemberTypes.Property)
    124.             {
    125.                 return (PropertyInfo)memberInfo;
    126.             }
    127.             return null;
    128.         }
    129.     }
    130. }

    This looks complicated but usage is pretty easy. You'll need to pass the property as an lambda expression (which turns out to something like "() => <property here>"), as well as the value you want to set it to. Even though the extension method takes a type parameter, it can usually be inferred from the objects you pass (the property lambda and the value) so you won't need to supply it.

    Usage examples:
    csharp Code:
    1. // Set label text
    2. label1.SetThreadSafeProperty(() => label1.Text, "test text");
    3.  
    4. // Disable button
    5. button1.SetThreadSafeProperty(() => button1.Enabled, false);
    6.  
    7. // Get TextBox text
    8. string txt = textBox1.GetThreadSafeProperty(() => textBox1.Text);
    9.  
    10. // Invoke some method
    11. customControl1.InvokeThreadSafeMethod(() => customControl1.SomeMethod("a", 3, 14.3));
    12.  
    13. // Invoke some function
    14. var x = customControl2.InvokeThreadSafeFunction(() => customControl2.SomeFunction(y))
    I'm not that much into threading yet, but it seems to work fine for a few little tests I've done.

    The only drawback is that it uses Reflection which can be a little slow, but it's only one call and I think it should be fine.

    I'm open to suggestions! One thing I don't much like is that it seems a little redundant to use the control name twice (first to call the extension method on, second to pass the property you want to get/set), but the only way I can think of to get around that is to pass the property name as a string, and I don't like that approach at all as it's error prone (no compile errors if you miss-spell it)...


    Enjoy.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width