我有一个具有两个DependencyProperty的用户控件。每个DependencyProperty都有PropertyChangedCallback。重要的是要在设置顺序属性值时调用回调。所以如果我写
Status = MyStatus.DataIsImporting;
var data = AsynchronouslyImportData();
Data = data;
我希望在数据的属性更改回调之前调用状态的属性更改回调。但是根据调试(找不到任何文档),回调调用的顺序是不确定的。有什么办法可以解决?
更新。上面看到的状态和数据未直接设置为用户控件实例。这些是ViewModel属性,可以通过绑定来控制用户控制属性。
Update2。我现在正在玩这个问题,并且有一个非常奇怪的解决方法。这是以前使用我的用户控件的方式:
<MyUserControl Status={Binding Status, Mode=TwoWay} Data={Binding Data}/>
我刚刚更改了绑定顺序,它起作用了!
<MyUserControl Data={Binding Data} Status={Binding Status, Mode=TwoWay}/>
它仍然表现为异步(看起来像绑定系统中存在某种消息循环),但是现在它以正确的顺序调用PropertyChangedCallback处理程序。
我正在搜索绑定顺序,有时会发现类似的问题(例如this one,但仍不清楚为什么会发生。
Update 3。我找到了一个真正的问题根源。使用我的控件的应用程序具有带有多个DataTemplates的ContentControl(取决于ViewModel类型)。当放置我的控件的DataTemplate不是最新的时(或者当您切换到其他DataTemplate并返回时),将发生描述的行为。我仍在澄清细节。
我可能应该以这个陈述开头这个答案:
“如果需要通过排列/排序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();
}
}
输出:
开始排队招标...变更状态...变更状态...变更状态...变更资料...变更资料...变更资料...变更资料...变更资料...变更资料...变更状态...处理状态从更改为1处理状态从1更改为73处理状态从73更改为57处理状态从57更改为33处理数据从到10的更改处理数据的更改从10更改为67处理数据的更改从67变为40处理数据的变化从40变为64处理数据从64到47的更改处理数据从47更改为81
DependencyProperties
。它们是TwoWay
绑定的源,但是不要求它们是DependencyProperties
。您可以使用TwoWay
绑定到任何标准属性,并实现INotifyPropertyChanged
以便在源值更改时通知目标。