2009-12-10 2 views
6

У меня естьINotifyPropertyChanged с резьбой

BindingList<T> 

, который привязан к DataGridView. Одно свойство в моем классе занимает много времени, чтобы вычислить, поэтому я выполнил действие. После вычисления я поднимаю событие OnPropertyChanged(), чтобы уведомить сетку, что значение уже готово.

По крайней мере, это теория. Но так как метод OnPropertyChanged вызывается из другого потока, я получаю некоторые перечеркнутые исключения в методе OnRowPrePaint сетки.

Может ли кто-нибудь сказать мне, как я должен исключить событие OnPropertyChanged в основной теме? Я не могу использовать Form.Invoke, так как класс MyClass не знает, что он работает в приложении Winforms.

public class MyClass : INotifyPropertyChanged 
{ 
    public int FastMember {get;set;} 

    private int? slowMember; 
    public SlowMember 
    { 
     get 
     { 
      if (slowMember.HasValue) 
       return slowMember.Value; 
      else 
      { 
       Thread t = new Thread(getSlowMember); 
       t.Start(); 
       return -1; 
      } 

     } 
    } 

    private void getSlowMember() 
    { 
     Thread.Sleep(1000); 
     slowMember = 5; 
     OnPropertyChanged("SlowMember"); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangingEventHandler eh = PropertyChanging; 
     if (eh != null) 
     { 
      eh(this, e); 
     } 
    } 

} 

ответ

8

По дизайну элемент управления может быть обновлен только потоком, в котором он был создан. Вот почему вы получаете исключения.

Рассмотрите возможность использования BackgroundWorker и обновите его только после завершения продолжительной операции, подписав обработчик событий на RunWorkerCompleted.

+0

Работает как очарование. До сих пор я не знал о BackgroundWorker. Это делает эту задачу настолько легкой, что много. –

1

Рассмотрение 1:
Посмотрите на класс UIThreadMarshal и его использование в этой статье:
UI Thread Marshaling in the Model Layer
Вы можете изменить класс от статического экземпляра и ввести его в свой объект. Таким образом, ваш объект не будет знать о классе Form. Он будет знать только о классе UIThreadMarshal.

Рассмотрение 2:
Я не думаю, что возврат -1 из вашей собственности - хорошая идея. Для меня это выглядит плохо.

Рассмотрение 3:
Возможно, ваш класс не должен использовать анховую резьбу. Возможно, это потребительские классы, которые должны решить, как назвать свою собственность: напрямую или в отдельном потоке. В этом случае, возможно, вам необходимо предоставить дополнительное свойство, такое как IsSlowMemberInitialized.

+0

К 1: Спасибо за ссылку. BackgroundWorker решил мою проблему в этом случае, но я уверен, что мои шорты мне понадобятся в ближайшем будущем. To 2: Вы правы, особенно beacause SlowMember может быть -1. Был только для тестирования To 3: Невозможно, поскольку DataGridView запрашивает значение (и получает -1 в первый раз, чем я обновляю значение и использую интерфейс INotifyPropertyChanged, чтобы сообщить datagridview измененного свойства, которое должно (Ok, я мог бы использовать таймер и проверить IsSlowMemberInitialized = true, но это уродливо В любом случае, так много. –

+0

Если вы используете DataGridView, то, возможно, вам нужно использовать BindingSource. В ссылке, которую я вам дал, существует реализация BindingSource, который поддерживает привязку от разных потоков. Вы можете работать над этим кодом, чтобы он был лучше подходит для ваших нужд. – nightcoder

2

Вот что я написал некоторое время назад; он должен работать разумно хорошо, но обратите внимание на стоимость лотов обновлений ...

using System.ComponentModel; 
using System.Threading; 
public class ThreadedBindingList<T> : BindingList<T> { 
    SynchronizationContext ctx = SynchronizationContext.Current; 
    protected override void OnAddingNew(AddingNewEventArgs e) { 
     if (ctx == null) { BaseAddingNew(e); } 
     else { ctx.Send(delegate { BaseAddingNew(e); }, null); } 
    } 
    protected override void OnListChanged(ListChangedEventArgs e) { 
     if (ctx == null) { BaseListChanged(e); } 
     else { ctx.Send(delegate { BaseListChanged(e); }, null); } 
    } 
    void BaseListChanged(ListChangedEventArgs e) { base.OnListChanged(e); } 
    void BaseAddingNew(AddingNewEventArgs e) { base.OnAddingNew(e); } 
} 
+0

Интересная реализация Marc, но поскольку это позволяет плохой дизайн, я считаю, что он должен использоваться только в определенных сценариях где вам действительно нужен элемент управления для обновления во время обработки действия. –

6

Люди иногда забывают, что обработчик событий является MultiCastDelegate и, как таковой, имеет всю информацию, касающуюся каждого абонента, что мы необходимо обработать эту ситуацию изящно, не налагая штраф за исполнение Invoke + Synchronization без необходимости. Я использую такой код для возрастов:

using System.ComponentModel; 
// ... 

public event PropertyChangedEventHandler PropertyChanged; 

protected virtual void OnPropertyChanged(string propertyName) 
{ 
    var handler = PropertyChanged; 
    if (handler != null) 
    { 
     var e = new PropertyChangedEventArgs(propertyName); 
     foreach (EventHandler h in handler.GetInvocationList()) 
     { 
      var synch = h.Target as ISynchronizeInvoke; 
      if (synch != null && synch.InvokeRequired) 
       synch.Invoke(h, new object[] { this, e }); 
      else 
       h(this, e); 
     } 
    } 
} 

Что она делает это просто, но я помню, что я чуть не треснул мой мозг тогда пытаюсь найти лучший способ сделать это.

Сначала он «захватывает» обработчик события на местном уровне, чтобы избежать любых условий гонки.

Если обработчик не является нулевым (в лизинге один абонент существует), он подготавливает события args, а затем выполняет итерацию через список вызовов этого многоадресного делегата.

Список вызовов имеет целевое свойство, которое является подписчиком события.Если этот абонент реализует ISynchronizeInvoke (все элементы управления пользовательского интерфейса реализуют его), мы затем проверяем его свойство InvokeRequired, и это правда, мы просто вызываем его, передавая делегат и параметры. Вызов этого способа синхронизирует вызов в потоке пользовательского интерфейса.

В противном случае мы просто вызываем делегат обработчика событий напрямую.

+2

Мне пришлось переименовать 'EventHandler' в' PropertyChangedEventHandler', потому что я получал 'System.InvalidCastException' с деталью' {«Невозможно наложить объект типа« System.ComponentModel.PropertyChangedEventHandler »на тип« System.EventHandler ».» } ' У меня есть BindingList, который создается в потоке пользовательского интерфейса, который подписывается на событие внутри, но переменная sync всегда возвращает значение null, поскольку h.Target имеет значение null. –

+0

У меня такая же проблема, как @RickShealer. Отмечая дату, я задаюсь вопросом, является ли это проблемой с более новыми версиями .Net? Это похоже на очень элегантное решение проблемы с перекрестной резьбой INotifyPropertyChanged, поэтому я надеюсь, что мы сможем заставить ее работать. – Jacob

+0

@Jacob Я буду нацелен на новые рамки, чтобы узнать, не сработает ли он. Скажите, пожалуйста, какая у вас структура вашего проекта? Целевая версия или любая другая информация, которая, по вашему мнению, уместна? – Loudenvier