Nicholas
Nicholas

Reputation: 793

DependencyProperty ignoring some of PropertyChanged calls

I Have an issue with my DependencyProperty. Say you have a timer that updates some UI element, if the callback is called once every 100ms which in turn updates the UI then i have no problem, however, if the timer is set to ~10ms for example, some of the calls will get ignored. I made a small solution that reproduces the problem:

This is a Custom UIElement with a dependency property:

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

This is a ViewModel that fires a timer and updates a property which in turn should call the CallBack on the DependencyProperty on CustomLabel.

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

And this is my View where the CustomLabel lives and the 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; }
}

So, I made some Debug.WriteLine() statements on each side of the DependencyProperty, the output looks like this:

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

Why is this happening and what can I do about it? Thanks for your time.

Upvotes: 3

Views: 521

Answers (1)

Glen Thomas
Glen Thomas

Reputation: 10744

The NotifyPropertyChanged event is handled by the Dispatcher, which uses a queue. The dispatcher is processing the events at a slower rate than they are being added to the queue.

Using a DispatcherTimer might allow you to update faster:

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

Also...

The System.Threading.Timer class that you are using does not, by default, have an accuracy capable of 10ms. It will use the operating system timer.

Quoting a Microsoft document on timer resolution:

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

It is possible to increase the timer resolution using calls to the Windows API, but this can cause battery drain.

Upvotes: 1

Related Questions