DependencyProperty 忽略了一些 PropertyChanged 调用

DependencyProperty ignoring some of PropertyChanged calls

我的 DependencyProperty 有问题。假设你有一个更新一些 UI 元素的计时器,如果回调每 100 毫秒调用一次,这又会更新 UI 那么我没问题,但是,如果计时器设置为 ~10ms例如,某些呼叫将被忽略。我做了一个重现问题的小解决方案:

这是自定义 UI元素,具有依赖性 属性:

public class CustomLabel : Label
{
    public float Range
    {
        get { return (float)GetValue(MaxRangeProperty); }
        set { SetValue(MaxRangeProperty, value); }
    }

    public static readonly DependencyProperty MaxRangeProperty =
        DependencyProperty.Register("Range", typeof(float), typeof(CustomLabel),
            new PropertyMetadata(0f, RangePropertyChanged));

    private static void RangePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var self = d as CustomLabel;
        Debug.WriteLine("CustomLabel");

        self.Content = self.Range;
    }
}

这是一个触发计时器并更新 属性 的 ViewModel,后者又应在 CustomLabel 的 DependencyProperty 上调用回调。

public class ViewModel : INotifyPropertyChanged
{
    Timer timer;
    Thread t;

    public ViewModel()
    {
        t = new Thread(() => timer = new Timer(new TimerCallback(CallBack), null, 0, 10));
        t.Start();
        Range = 100;
    }

    void CallBack(object state)
    {
        Range = (new Random()).Next(0, 1000);
    }

    private float _range;
    public float Range
    {
        get { return _range; }
        set
        {
            if (_range != value)
            {
                _range = value;
                NotifyPropertyChanged();
                Debug.WriteLine("ViewModel");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

这是我的 CustomLabel 和 ViewModel 所在的视图:

<Window x:Class="TimerTest.MainWindow"
        xmlns:local="clr-namespace:TimerTest"
        Title="MainWindow">
    <Grid>
        <local:CustomLabel x:Name="customLabel" Range="{Binding Range}"/>
    </Grid>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ViewModel = new ViewModel();
        customLabel.DataContext = ViewModel;
    }

    public ViewModel ViewModel { get; set; }
}

因此,我在 DependencyProperty 的每一侧都做了一些 Debug.WriteLine() 语句,输出如下所示:

  100ms          10ms
CustomLabel      ViewModel        
ViewModel        CustomLabel
CustomLabel      ViewModel        
ViewModel        ViewModel        
CustomLabel      CustomLabel
ViewModel        ViewModel        
CustomLabel      ViewModel        
ViewModel        ViewModel        
CustomLabel      ViewModel 
ViewModel        CustomLabel

为什么会这样,我该怎么办? 谢谢你的时间。

NotifyPropertyChanged 事件由使用队列的 Dispatcher 处理。调度程序处理事件的速度低于将事件添加到队列的速度。

使用 DispatcherTimer 可能会让您更新得更快:

DispatcherTimer timer =
    new DispatcherTimer(TimeSpan.FromMilliseconds(10),
                    DispatcherPriority.Normal,
                    delegate
                    {
                        MyCustomLabel.SetValue(MaxRangeProperty, viewModel.Range);
                    },
                    Dispatcher);

还有...

您使用的 System.Threading.Timer class 默认情况下不具有 10 毫秒的精度。它将使用操作系统定时器。

引用有关计时器分辨率的 Microsoft 文档:

The default timer resolution on Windows 7 is 15.6 milliseconds (ms)

可以通过调用 Windows API 来增加计时器分辨率,但这会导致电池耗尽。