当我尝试从自定义面板控件读取属性时,出现了这个错误。该属性返回面板内文本框的值。如何从另一个线程读取返回文本框控件的值的属性?以下是我的属性代码示例。我不担心setter。
以下是确切的错误信息: 跨线程操作无效:从创建它的线程以外的线程访问控件''。
public string Header
{
get
{
return _HeaderComboBox.Text;
}
set
{
_HeaderComboBox.Text = value;
}
}
当我尝试从自定义面板控件读取属性时,出现了这个错误。该属性返回面板内文本框的值。如何从另一个线程读取返回文本框控件的值的属性?以下是我的属性代码示例。我不担心setter。
以下是确切的错误信息: 跨线程操作无效:从创建它的线程以外的线程访问控件''。
public string Header
{
get
{
return _HeaderComboBox.Text;
}
set
{
_HeaderComboBox.Text = value;
}
}
根据您发布的getter代码片段,以下是我如何实现此示例:
public string Header {
get {
string text = string.Empty;
_HeaderComboBox.BeginInvoke(new MethodInvoker(delegate {
text = _HeaderComboBox.Text;
}));
return text;
}
set {
_HeaderComboBox.Text = value;
}
}
有更加优雅的方法,但这只是一个通用示例。
为了访问属性,您需要将回调封送到UI线程。
在.NET 2.0之前,您必须调用Control类上的Invoke方法,以便将调用封送到Text属性。
在.NET 2.0及更高版本中,如果您的后台线程可以访问UI线程的SynchronizationContext,则可以调用Send方法将回调封送回UI。
请注意,如果您不需要等待调用的结果(就像在这里一样,因为您想要调用Text属性的结果),则可以分别在Control和SynchronizationContext上调用BeginInvoke和Post。但是,在此之前,请注意。BeginInvoke 和 Post。除了创建它的UI线程外,您无法在任何其他线程上访问WinForms控件,因为存在跨线程问题、竞争条件等。要解决此问题,您必须在UI线程上运行想要运行的任何命令。这可以通过使用Invoke方法来完成:
public void InvokeExample()
{
if (InvokeRequired)
{
// Invoke this method on the UI thread using an anonymous delegate
Invoke(new MethodInvoker(() => InvokeExample()));
return;
}
string header = Control.Header;
}
我在类似的帖子中找到了答案: 如何从另一个线程更新GUI?
我扩展了它,用这个自定义类来扩展System.Windows.Forms.Control
对象,其中包含SetPropertyThreadSafe
和GetPropertyThreadSafe
。
要使用这个扩展,你只需要像这样包含命名空间:
using ControlExtention;
以下是我的代码:
using System;
using System.Linq.Expressions;
using System.Windows.Forms;
namespace ControlExtention
{
public static class ControlExtentions
{
// https://dev59.com/wnRB5IYBdhLWcg3wUVtd
// Usage: myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
private delegate void SetPropertyThreadSafeDelegate<TResult>(Control @this, Expression<Func<TResult>> property, TResult value);
/// <summary>
/// Set property from a Thread other than the UI Thread safely.
/// Use with Lambda-Expression: ControlObject.SetPropertyThreadSafe(() => ControlObject.Property, NewPropertyValue);
/// </summary>
/// <typeparam name="TResult">Do not set.</typeparam>
/// <param name="this">Use lambda expression.</param>
/// <param name="property">Use lambda expression.</param>
/// <param name="value">Use lambda expression.</param>
public static void SetPropertyThreadSafe<TResult>(this Control @this, Expression<Func<TResult>> property, TResult value)
{
System.Reflection.PropertyInfo propertyInfo = (property.Body as MemberExpression).Member as System.Reflection.PropertyInfo;
// check ob property überhaupt ein teil von this ist
if (propertyInfo == null || !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>(SetPropertyThreadSafe), new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(propertyInfo.Name, System.Reflection.BindingFlags.SetProperty, null, @this, new object[] { value });
}
}
private delegate TResult GetPropertyThreadSafeDelegate<TResult>(Control @this, Expression<Func<TResult>> property);
/// <summary>
/// Get property from a Thread other than the UI Thread safely.
/// Use with Lambda-Expression: value = ControlObject.GetPropertyThreadSafe(() => ControlObject.Property);
/// </summary>
/// <typeparam name="TResult">Do not set.</typeparam>
/// <param name="this">Use lambda expression.</param>
/// <param name="property">Use lambda expression.</param>
/// <param name="value">Use lambda expression.</param>
public static TResult GetPropertyThreadSafe<TResult>(this Control @this, Expression<Func<TResult>> property)
{
System.Reflection.PropertyInfo propertyInfo = (property.Body as MemberExpression).Member as System.Reflection.PropertyInfo;
// check ob property überhaupt ein teil von this ist
if (propertyInfo == null || !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
return (TResult)@this.Invoke(new GetPropertyThreadSafeDelegate<TResult>(GetPropertyThreadSafe), new object[] { @this, property });
}
else
{
return (TResult)@this.GetType().InvokeMember(propertyInfo.Name, System.Reflection.BindingFlags.GetProperty, null, @this, new object[] { });
}
}
}
}