1

I have a user control which have two DependencyProperties. Each DependencyProperty has PropertyChangedCallback. It is important callbacks to be called in the order properties values are set. So if I write

Status = MyStatus.DataIsImporting;

var data = AsynchronouslyImportData();

Data = data;

I want Status's property changed callback to be called before Data's property changed callback. But according to debugging (can't find any documentation about it) the order of callbacks calls is undefined. Is there any way to resolve it?

Update. The Status and Data you see above are not set directly to user control instance. These are ViewModel properties which are to user control properties through bindings.

Update2. I was playing with this problem now and had a very strange fix. Here is how my user control was used before:

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

I have just changed the bindings order and it worked!

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

It still behaves asynchronously (looks like there is kind of message loop inside binding system) but now it calles PropertyChangedCallback handlers in the right order.

I am googling about bindings order and from time to time find similar problems (for example, this one) but it is still unclear why it happens.

Update 3. I have found a real source of problem. The application which uses my control has ContentControl with several DataTemplates (depending of ViewModel type). Described behaviour happens when DataTemplate where my control is placed is not current (or when you switch to other DataTemplate and back). I am still clarifying the details.

Community
  • 1
  • 1
SiberianGuy
  • 24,674
  • 56
  • 152
  • 266
  • 1
    When you say 'according to debugging', what exactly do you mean? Are you debugging this and they are not always happening in the desired order? Is the code above being called from the UI Thread? – Brian S Mar 18 '13 at 16:55
  • 1
    Is there a reason these need to be `DependencyProperties` rather than standard properties that raise NotifyPropertyChanged? Most ViewModels implement `INotifyPropertyChanged`, but are not `DependencyObjects`. With `DependencyProperties`, you have less control over the binding notification. – Brian S Mar 18 '13 at 17:17
  • 2
    `var data = AsynchronouslyImportData();` is not asynchronous, so your question appears to be the standard "my UI isn't changing while I block the UI thread" problem. Which is solved by doing your work on some other thread. –  Mar 18 '13 at 17:18
  • @BrianS, I am doing TwoWay binding at least. As I know you need to use DependencyProperties for that – SiberianGuy Mar 20 '13 at 17:13
  • @Will, nothing like that – SiberianGuy Mar 20 '13 at 17:13
  • 1
    @Idsa: The callbacks WILL be called in order. Usually in cases like this (sorry, can't really tell from your question), your code would be executed all in order, but because it is synchronous code executed in the UI thread you wouldn't see anything until the process is completed. That's why I have a +1 on my above comment. You need to show your actual code, or at a minimum repro in a [sscce.org](http://sscce.org) that demonstrates your issue. –  Mar 20 '13 at 17:28
  • 1
    @Will +1 for analyzing the "root cause" rather than blinding plopping out code, like I did. :P – JerKimball Mar 20 '13 at 18:15
  • @ldsa `DependencyProperties` are not required for TwoWay binding. The only reason you need a `DependencyProperty` is if the property will be the Target of a `Binding`, or if you're going to animate a property value with a trigger or something. I'd recommend changing them to standard properties that raise PropertyChanged and you'll be guaranteed of the order of notification. – Brian S Mar 20 '13 at 22:01
  • @BrianS: DPs work just like any property. There is no issue here using DPs. –  Mar 21 '13 at 14:22
  • @Will, DPs are very different from other properties. The reasons are numerous and varied, but for purposes of this conversation, the relevant fact is that the framework controls the property value. See [this link](http://msdn.microsoft.com/en-us/library/ms752914.aspx#setting_properties_data_binding) for more. – Brian S Mar 21 '13 at 14:33
  • 1
    @BrianS: Yes, I am very familiar with DPs, thank you. Your comments "the only reason you need" and the implication that execution order isn't guaranteed are incorrect. Have had this convo with others of your opinion, so I can tell you that there isn't need to rehash in the comments here. DPs and INPC both have their advantages and disadvantages, and I use them both depending on which I need. My point was that DP/not DP has nothing to do with the OP, and a side note that you shouldn't automatically reject using DPs in your VMs. –  Mar 21 '13 at 14:38
  • @Will - not trying to start an argument here, so sorry if I came off rude. I don't agree "DPs work just like any property" and was trying to address that statement, not be a jerk. My suggestion of the "the only reason you need" was in the context of addressing the previous comment. Yes, I agree there are many reasons to use DPs in numerous places, and I call out some of the other reasons in that same comment. The goal here is to help solve the problem, and from my understanding, using DPs is not necessary in this scenario and may solve the problem. – Brian S Mar 21 '13 at 15:12
  • @BrianS: No no, its just comments, you weren't coming off rude, and I certainly don't mean my comments to be rude. I always assume there's a smiley face at the end of every sentence :) No worries. –  Mar 21 '13 at 15:45

2 Answers2

0

I should probably preface this answer with this statement:

"If you need to order the changes made to a DependencyProperty by arranging/ordering the sequence of DependencyPropertyChangedCallbacks, you're probably doing it wrong."

That said, here's some idle thrown-together code that kinda does what you're talking about:

The object:

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);
            }
        }
    }
}

And the test:

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();
    }
}

Output:

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
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
JerKimball
  • 16,584
  • 3
  • 43
  • 55
  • As I said in the question (and double in question comment), AsynchronouslyImportData works asynchronously (it is just schematic code). And my problem doesn't have any relation to busy UI thread. – SiberianGuy Mar 23 '13 at 10:55
  • Fair point, I was just trying to show a way you *could* order the invocation of callbacks – JerKimball Mar 24 '13 at 18:31
-1

Per your comments, these properties do not need to be DependencyProperties. They are the Source of a TwoWay Binding, however this does not require them to be DependencyProperties. You can use a TwoWay Binding to any standard property, and implement INotifyPropertyChanged in order to notify the target when the source value changes.

A Binding Target must be a DependencyProperty, but not the Source, even for a TwoWay Binding (TextBoxes bind TwoWay by default, and you can bind them to standard Properties).

If you are manually raising property changed notifications, these actions will occur sequentially on the same Thread, and you'll be guaranteed that the notifications occur in order. With DependencyProperties you are letting the framework manage the values and notification, and there's no guarantee of the order.

DependencyProperties are rarely necessary in ViewModels. Most of the time, you only need your ViewModel to implement INotifyPropertyChanged and raise property change notifications.

Brian S
  • 5,675
  • 1
  • 22
  • 22