2013-03-18 2 views
0

У меня есть пользовательский элемент управления, у которого есть два DependencyProperties. Каждый DependencyProperty имеет свойство PropertyChangedCallback. Важные обратные вызовы, вызываемые в значениях свойств заказа, устанавливаются. Поэтому, если я пишуПорядок выполнения PropertyChangedCallbacks

Status = MyStatus.DataIsImporting; 

var data = AsynchronouslyImportData(); 

Data = data; 

Я хочу, чтобы свойство свойства изменило обратный вызов, который будет вызван до того, как свойство Data изменило обратный вызов. Но согласно отладке (не может найти никакой документации об этом) порядок вызовов обратных вызовов не определен. Есть ли способ разрешить это?

Обновление. Состояние и данные, которые вы видите выше, не устанавливаются непосредственно в пользовательский экземпляр пользователя. Это свойства ViewModel, которые соответствуют свойствам управления пользователями посредством привязок.

Обновление2. Сейчас я играл с этой проблемой и имел очень странное решение. Вот как мой пользовательский контроль использовался раньше:

<MyUserControl Status={Binding Status, Mode=TwoWay} Data={Binding Data}/> 

Я только что изменил порядок привязки, и это сработало!

<MyUserControl Data={Binding Data} Status={Binding Status, Mode=TwoWay}/> 

Он по-прежнему ведет себя асинхронно (похоже, что это своего рода цикл сообщений внутри системы связывания), но теперь он Кальесы PropertyChangedCallback обработчиков в правильном порядке.

Я занимаюсь поиском по связям и время от времени нахожу подобные проблемы (например, this one), но до сих пор неясно, почему это происходит.

Обновление 3. Я нашел настоящий источник проблем. Приложение, использующее мой элемент управления, имеет ContentControl с несколькими DataTemplates (в зависимости от типа ViewModel). Описанное поведение происходит, когда DataTemplate, где находится мой элемент управления, не является текущим (или когда вы переключаетесь на другой DataTemplate и обратно). Я все еще уточняю детали.

+1

Когда вы говорите «в соответствии с отладкой», что именно вы имеете в виду? Вы отлаживаете это, и они не всегда происходят в желаемом порядке? Вызывается ли код выше из потока пользовательского интерфейса? –

+0

@BrianS, да и да – SiberianGuy

+1

Есть ли причина, по которой они должны быть 'DependencyProperties', а не стандартные свойства, которые повышают NotifyPropertyChanged? Большинство ViewModels реализуют 'INotifyPropertyChanged', но не являются' DependencyObjects'. С 'DependencyProperties' вы имеете меньший контроль над уведомлением о привязке. –

ответ

0

я, вероятно, следует предварить этот ответ с этим утверждением:

«Если вам необходимо заказать изменения, внесенные в DependencyProperty путем организации/упорядочения последовательности DependencyPropertyChangedCallbacks, вы, вероятно, делают это неправильно.»

Тем не менее, вот некоторые простаивает брошено-вместе код, который своего рода делает то, что вы говорите:

Объект:

public class SomeThing : DependencyObject, IDisposable 
{ 
    public static readonly DependencyProperty StatusProperty = 
     DependencyProperty.Register(
      "Status", 
      typeof(string), 
      typeof(SomeThing), 
      new FrameworkPropertyMetadata(OnStatusChanged)); 
    public static readonly DependencyProperty DataProperty = 
     DependencyProperty.Register(
      "Data", 
      typeof(string), 
      typeof(SomeThing), 
      new FrameworkPropertyMetadata(OnDataChanged)); 

    // The OrderedBag is from the Wintellect.PowerCollections, 
    // as I was too lazy to write my own PriorityQueue-like implementation 
    private static OrderedBag<Tuple<int, DependencyObject, DependencyPropertyChangedEventArgs>> _changeQueue = 
     new OrderedBag<Tuple<int, DependencyObject, DependencyPropertyChangedEventArgs>>((l,r) => l.Item1.CompareTo(r.Item1)); 

    private static object _syncRoot = new object(); 
    private static Task queueTenderTask; 
    private static CancellationTokenSource canceller; 

    static SomeThing() 
    { 
     canceller = new CancellationTokenSource(); 
     queueTenderTask = Task.Factory.StartNew(queueTender); 
    } 

    public string Status 
    { 
     get { return (string)this.GetValue(StatusProperty); } 
     set { this.SetValue(StatusProperty, value); } 
    } 
    public string Data 
    { 
     get { return (string)this.GetValue(DataProperty); } 
     set { this.SetValue(DataProperty, value); } 
    } 

    public void Dispose() 
    { 
     if(canceller != null) 
     { 
      canceller.Cancel(); 
      if(queueTenderTask != null) 
      { 
       queueTenderTask.Wait(); 
      } 
     } 
    } 

    private static void OnStatusChanged(
     DependencyObject dobj, 
     DependencyPropertyChangedEventArgs args) 
    { 
     lock(_syncRoot) 
     { 
      _changeQueue.Add(Tuple.Create(0, dobj, args)); 
     } 
    } 
    private static void OnDataChanged(
     DependencyObject dobj, 
     DependencyPropertyChangedEventArgs args) 
    { 
     lock(_syncRoot) 
     { 
      _changeQueue.Add(Tuple.Create(1, dobj, args)); 
     } 
    } 
    private static void ProcessChange(
     Tuple<int, DependencyObject,DependencyPropertyChangedEventArgs> pair) 
    { 
     // do something useful? 
     Console.WriteLine(
      "Processing change on {0} from {1} to {2}", 
      pair.Item3.Property.Name, 
      pair.Item3.OldValue, 
      pair.Item3.NewValue); 
    } 

    private static void queueTender() 
    { 
     Console.WriteLine("Starting queue tender..."); 
     var shouldCancel = canceller.IsCancellationRequested; 
     while(!shouldCancel) 
     { 
      lock(_syncRoot) 
      { 
       if(_changeQueue.Count > 0) 
       { 
        var nextUp = _changeQueue[0]; 
        _changeQueue.RemoveFirst();  
        ProcessChange(nextUp); 
       } 
      } 
      for(int i=0;i<10;i++) 
      { 
       shouldCancel = canceller.IsCancellationRequested; 
       if(shouldCancel) break; 
       Thread.Sleep(10); 
      } 
     } 
    } 
} 

И тест:

void Main() 
{ 
    var rnd = new Random(); 
    using(var ob = new SomeThing()) 
    { 
     for(int i=0;i<10;i++) 
     { 
      if(rnd.NextDouble() > 0.5) 
      { 
       Console.WriteLine("Changing Status..."); 
       ob.Status = rnd.Next(0, 100).ToString(); 
      } 
      else 
      { 
       Console.WriteLine("Changing Data..."); 
       ob.Data = rnd.Next(0, 100).ToString(); 
      } 
     } 
     Console.ReadLine(); 
    } 
} 

Выход:

Starting queue tender... 
Changing Status... 
Changing Status... 
Changing Status... 
Changing Data... 
Changing Data... 
Changing Data... 
Changing Data... 
Changing Data... 
Changing Data... 
Changing Status... 
Processing change on Status from to 1 
Processing change on Status from 1 to 73 
Processing change on Status from 73 to 57 
Processing change on Status from 57 to 33 
Processing change on Data from to 10 
Processing change on Data from 10 to 67 
Processing change on Data from 67 to 40 
Processing change on Data from 40 to 64 
Processing change on Data from 64 to 47 
Processing change on Data from 47 to 81 
+0

Как я уже сказал в вопросе (и двойной вопрос), AsynchronouslyImportData работает асинхронно (это всего лишь схематический код). И моя проблема не имеет никакого отношения к занятому потоку пользовательского интерфейса. – SiberianGuy

+0

Справедливая точка, я просто пытался показать способ, которым вы * можете * заказать вызов обратных вызовов – JerKimball

0

На ваш комментарий, эти свойства не обязательно должны быть DependencyProperties. Они являются источником привязки TwoWay, однако это не требует от них DependencyProperties. Вы можете использовать привязку TwoWay к любому стандарту, а также реализовать INotifyPropertyChanged, чтобы уведомить цель, когда изменяется исходное значение.

Binding Цель должна быть DependencyProperty, но не источник, даже для TwoWay Binding (TextBoxes привязки TwoWay по умолчанию, и вы можете связать их со стандартными Свойства).

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

DependencyProperties редко присутствуют в ViewModels. В большинстве случаев вам нужна только ваша ViewModel для реализации INotifyPropertyChanged и повышения уведомлений об изменении свойств.

+0

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

Смежные вопросы