6

Tôi muốn tạo proxy động cho các điều khiển WinForms gắn kết với các đối tượng được thay đổi bằng một chuỗi khác (không phải GUI). Một proxy như vậy sẽ ngăn chặn sự kiện PropertyChanged và gửi nó bằng cách sử dụng SynchronizationContext thích hợp.Tạo proxy INotifyPropertyChanged để gửi các cuộc gọi đến chuỗi giao diện người dùng

Bằng cách đó tôi có thể sử dụng lớp trợ giúp để thực hiện công việc mà không phải thực hiện đồng bộ hóa theo cách thủ công mỗi lần (if (control.InvokeRequired) etc.).

Có cách nào để thực hiện điều đó bằng cách sử dụng LinFu, Castle hoặc thư viện tương tự không?

[Chỉnh sửa]

Nguồn số liệu không nhất thiết phải là một danh sách. Nó có thể là bất kỳ đối tượng kinh doanh, ví dụ:

interface IConnection : INotifyPropertyChanged 
{ 
    ConnectionStatus Status { get; } 
} 

tôi có thể tạo ra một wrapper mà có thể thực hiện công việc, và nó sẽ giống như thế này:

public class ConnectionWrapper : IConnection 
{ 
    private readonly SynchronizationContext _ctx; 
    private readonly IConnection _actual; 
    public ConnectionWrapper(IConnection actual) 
    { 
     _ctx = SynchronizationContext.Current; 
     _actual= actual; 
     _actual.PropertyChanged += 
      new PropertyChangedEventHandler(actual_PropertyChanged); 
    } 

    // we have to do 2 things: 
    // 1. wrap each property manually 
    // 2. handle the source event and fire it on the GUI thread 

    private void PropertyChanged(object sender, PropertyChangedEvArgs e) 
    { 
     // we will send the same event args to the GUI thread 
     _ctx.Send(delegate { this.PropertyChanged(sender, e); }, null); 
    } 

    public ConnectionStatus Status 
    { get { return _instance.Status; } } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

(có thể có một số sai sót trong này mã, tôi đang làm cho nó lên)

Những gì tôi muốn làm là để có một proxy động (Reflection.Emit) một lớp lót cho điều này, ví dụ

IConnection syncConnection 
     = new SyncPropertyChangedProxy<IConnection>(actualConnection); 

và tôi muốn biết nếu điều gì đó như thế này có thể sử dụng triển khai proxy động hiện tại hay không.

Câu hỏi tổng quát hơn sẽ là: Làm cách nào để chặn sự kiện khi tạo proxy động? Các thuộc tính chặn (ghi đè) được giải thích tốt trong mọi triển khai.

[Edit2]

Lý do (tôi nghĩ) Tôi cần một proxy là stack trace trông như thế này:

 
at PropertyManager.OnCurrentChanged(System.EventArgs e) 
at BindToObject.PropValueChanged(object sender, EventArgs e) 
at PropertyDescriptor.OnValueChanged(object component, EventArgs e) 
at ReflectPropertyDescriptor.OnValueChanged(object component, EventArgs e) 
at ReflectPropertyDescriptor.OnINotifyPropertyChanged(object component, 
    PropertyChangedEventArgs e)  
at MyObject.OnPropertyChanged(string propertyName) 

Bạn có thể thấy rằng BindToObject.PropValueChanged không vượt qua các ví dụ sender đến PropertyManager và Trình phản chiếu hiển thị đối tượng người gửi không được tham chiếu ở bất kỳ đâu. Nói cách khác, khi sự kiện PropertyChanged được kích hoạt, thành phần sẽ sử dụng sự phản chiếu để truy cập thuộc tính của nguồn dữ liệu gốc (bound) gốc (bị ràng buộc).

Nếu tôi bọc đối tượng trong một lớp chỉ chứa sự kiện (như Sam được đề xuất), lớp trình bao bọc như vậy sẽ không chứa bất kỳ thuộc tính nào có thể được truy cập thông qua Reflection.

+0

Xem 'ThreadedBindingList' - nó đã được lặp đi lặp lại ở đây trên SO (http://stackoverflow.com/questions/455766/how-do-you -chính xác-cập nhật-một-databound-datagridview-từ-một-nền-thread). –

Trả lời

4

Đây là lớp sẽ bọc một INotifyPropertyChanged, chuyển tiếp sự kiện PropertyChanged thông qua SynchronizationContext.Current và chuyển tiếp thuộc tính.

Giải pháp này sẽ hoạt động, nhưng đôi khi nó có thể được cải thiện để sử dụng biểu thức lambda thay vì tên thuộc tính. Điều đó sẽ cho phép loại bỏ sự phản chiếu, cung cấp quyền truy cập được nhập vào thuộc tính. Biến chứng với điều này là bạn cũng cần phải lấy cây biểu thức từ lambda để lấy ra tên thuộc tính để bạn có thể sử dụng nó trong phương thức OnSourcePropertyChanged. Tôi thấy một bài viết về việc kéo một tên thuộc tính từ một cây biểu thức lambda nhưng tôi không thể tìm thấy nó ngay bây giờ.

Để sử dụng lớp này, bạn muốn thay đổi ràng buộc của bạn như thế này:

Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value"); 

Và đây là SyncBindingWrapper:

using System.ComponentModel; 
using System.Reflection; 
using System.Threading; 

public class SyncBindingWrapper<T> : INotifyPropertyChanged 
{ 
    private readonly INotifyPropertyChanged _source; 
    private readonly PropertyInfo _property; 

    public event PropertyChangedEventHandler PropertyChanged; 

    public T Value 
    { 
     get 
     { 
      return (T)_property.GetValue(_source, null); 
     } 
    } 

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName) 
    { 
     _source = source; 
     _property = source.GetType().GetProperty(propertyName); 
     source.PropertyChanged += OnSourcePropertyChanged; 
    } 

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName != _property.Name) 
     { 
      return; 
     } 
     PropertyChangedEventHandler propertyChanged = PropertyChanged; 
     if (propertyChanged == null) 
     { 
      return; 
     } 

     SynchronizationContext.Current.Send(state => propertyChanged(this, e), null); 
    } 
} 
+1

Cảm ơn, đây là cơ bản những gì tôi đã làm ở cuối, quên chấp nhận nó. Điều làm tôi băn khoăn là tôi cứ nghĩ rằng mình cần một wrapper duy nhất cho một đối tượng, trong khi thực sự tôi cần bọc mỗi thuộc tính * trong một trình bao bọc khác để làm cho nó hoạt động. – Groo

+0

Điều này đơn giản là rực rỡ! –

+0

Chỉ tìm thấy điều này, chính xác những gì tôi đang tìm kiếm. Một điều mặc dù, đối với bất cứ ai khác mà đi qua là: SyncBindingWrapper nên cung cấp một phương tiện để loại bỏ chính nó từ sự kiện PropertyChanged của đối tượng nguồn, có lẽ bằng cách thực hiện IDisposable. – SimonC

2

Tôi đã bắt gặp những vấn đề tương tự và giải pháp Samuel của didn' t làm việc cho tôi, vì vậy tôi đã đặt khởi tạo bối cảnh đồng bộ hóa trong hàm tạo và tên thuộc tính "Value" phải được chuyển thay vì thuộc tính ban đầu. Điều này làm việc cho tôi:

public class SyncBindingWrapper: INotifyPropertyChanged 
{ 
    private readonly INotifyPropertyChanged _source; 
    private readonly PropertyInfo _property; 

    public event PropertyChangedEventHandler PropertyChanged; 

    private readonly SynchronizationContext _context; 

    public object Value 
    { 
     get 
     { 
      return _property.GetValue(_source, null); 
     } 
    } 

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName) 
    { 
     _context = SynchronizationContext.Current; 
     _source = source; 
     _property = source.GetType().GetProperty(propertyName); 
     source.PropertyChanged += OnSourcePropertyChanged; 
    } 

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     var propertyChanged = PropertyChanged; 
     if (propertyChanged != null && e.PropertyName == _property.Name) 
     { 
      _context.Send(state => propertyChanged(this, new PropertyChangedEventArgs("Value")), null); 
     } 
    } 
} 

Cách sử dụng:

_textBox1.DataBindings.Add("Text", new SyncBindingWrapper(someObject, "SomeProperty"), "Value"); 
+0

Có, cảm ơn, IIRC Tôi cũng đã sửa phương thức theo cách tương tự nhưng quên cập nhật. Sử dụng 'SynchronizationContext.Current' trong' OnSourcePropertyChanged' sẽ không có ý nghĩa. – Groo

Các vấn đề liên quan