DependencyProperty ignoring some of PropertyChanged calls - c#

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.

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.

Related

Binding an integer member of an class object to Textbox in WPF with INotifyPropertyChanged

I am absolute beginner in WPF and C#.
I am trying to populate the textbox with a counter after reading about INotifyPropertyChanged.
Below is my code:
namespace DataBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
//https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-property-change-notification?view=netframeworkdesktop-4.8
public class OPCUAData : INotifyPropertyChanged
{
private string m_text;
private int m_int;
public event PropertyChangedEventHandler PropertyChanged;
public OPCUAData()
{ }
public OPCUAData(string str, int i)
{
this.m_text = str;
this.m_int = i;
}
public string OPCUAtext
{
get { return m_text; }
set
{
if (value != m_text)
{
m_text = value;
OnPropertyChanged();
}
}
}
public int OPCUAint
{
get { return m_int; }
set
{
if (value != m_int)
{
m_int = value;
OnPropertyChanged();
}
}
}
// Create the OnPropertyChanged method to raise the event
// The calling member's name will be used as the parameter.
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public partial class MainWindow : Window
{
static OPCUAData opc = new OPCUAData("", 5);
public MainWindow()
{
InitializeComponent();
Thread thread = new Thread(incrementData);
thread.Start();
}
void incrementData()
{
Stopwatch timer = new Stopwatch();
timer.Start();
while (timer.Elapsed.TotalSeconds < 10)
{
opc.OPCUAint = opc.OPCUAint + 1;
}
timer.Stop();
}
}
}
I am expecting my variable opc.OPCUAint to be displayed in the textbox but it is not. Please help about what I am missing here.
Below is the xml.
<Window x:Class="DataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataBinding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox Text="{Binding ElementName=opc, Path= OPCUAint }" Margin="91,56,454.6,286"></TextBox>
</Grid>
</Window>
Set the DataContext of your window:
public MainWindow()
{
InitializeComponent();
DataContext = opc; // <--
Thread thread = new Thread(incrementData);
thread.Start();
}
...and bind directly to a property of it:
<TextBox Text="{Binding OPCUAint}" />
If you want to update the UI from a background thread, you should run the code in Application's dispatcher
Application.Current.Dispatcher.Invoke(() => opc.OPCUAint = opc.OPCUAint + 1);
You use a StopWatch to measure time.
You use a timer to execute an operation periodically.
For example use Timer to execute the operation on a background thread or use DispatcherTimer to execute the operation on the UI thread.
public void StartTimer()
{
// The callback will automatically execute on a background tread.
// Note that Timer implements IDisposable
var timer = new Timer(PeriodicOperation);
// Start the timer.
timer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
private void PeriodicOperation(object state)
{
opc.OPCUAint++;
}
If incrementing is the only operation, or you don't execute CPU intensive code in general, don't create a dedicated thread. It will make the performance unnecessarily bad. In this case, and in case of your posted example, use the mentioned DispatcherTimer:
public void StartTimer()
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += PeriodicOperation;
dispatcherTimer.Interval = TimeSpan.Fromseconds(1);
dispatcherTimer.Start();
}
private void PeriodicOperation(object sender, EventArgs e)
{
opc.OPCUAint++;
}
Then finally to make it work, you must assign the OPCUAData instance to the DataContext of the MainWindow. Avoid defining public static fields or properties:
private OPCUAData OPCUAData { get; }
public MainWindow()
{
InitializeComponent();
this.OPCUAData = new OPCUAData("", 5);
this.DataContext = this.OPCUAData;
StartTimer();
}
public void StartTimer()
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += PeriodicOperation;
dispatcherTimer.Interval = TimeSpan.Fromseconds(1);
dispatcherTimer.Start();
}
private void PeriodicOperation(object sender, EventArgs e)
{
this.OPCUAData.OPCUAint++;
}
And bind directly to the DataContext:
<Window>
<TextBox Text="{Binding OPCUAint}" />
</Window>

Xamarin Timer crashes

So I've done this in code:
public partial class Timer : ContentView , INotifyPropertyChanged
{
private int seconds = 30;
private System.Timers.Timer timer;
public Timer()
{
InitializeComponent();
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(OneSecondPassed);
timer.Enabled = true;
}
private void OneSecondPassed(object source, System.Timers.ElapsedEventArgs e)
{
seconds--;
Time = seconds.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
public string Time
{
get => seconds.ToString();
set
{
Time = value;
if (PropertyChanged != null)
{
PropertyChanged(this , new PropertyChangedEventArgs("Time"));
}
}
}
}
and then bound my label's text to it in XAML:
<Label BindingContext ="{x:Reference this}"
Text="{Binding Time}"/>
When I start the app, it crashes...I don't really understand how PropertyChanged works, just that INotifyPropertyChanged implements it.Also, when I declare PropertyChanged, it tells me that BindableObject.PropertyChanged already exists, use new in order to hide it.If you could explain how the interface and its event works, I'd be really thankful.
your setter is creating an infinite loop.
set
{
// this will call the setter again, infinitely
Time = value;
...
}
you already have a private variable for seconds, you should use it here
public int Time
{
get => seconds;
set
{
seconds = value;
if (PropertyChanged != null)
{
PropertyChanged(this , new PropertyChangedEventArgs("Time"));
}
}
}
private void OneSecondPassed(object source, System.Timers.ElapsedEventArgs e)
{
Time--;
}
when I declare PropertyChanged, it tells me that BindableObject.PropertyChanged already exists
I see that Timer inherits from ContentView.
ContentView is a BindableObject, so it already implements everything used in binding, such as PropertyChanged. Delete your declaration of PropertyChanged.
OPTIONAL: You could also remove , INotifyPropertyChanged - ContentView does that for you. However it is harmless to leave it there.

Continuous data update with MVVM

I'm trying to learn the MVVM structure. How can I update a variable that changes constantly in another class in the UI.
I created a simple example because the project codes are too much. But I failed.
I would be very grateful if you could tell me where I went wrong. Thanks.
MyModel
public class Temperature : INotifyPropertyChanged
{
private double _memsTemperature;
private double _cpuTemperature;
private double _animalTemperature;
public double MemsTemperature
{
get { return _memsTemperature; }
set
{
_memsTemperature = value;
OnPropertyChanged("MemsTemperature");
}
}
public double CpuTemperature
{
get { return _cpuTemperature; }
set
{
_cpuTemperature = value;
OnPropertyChanged("CpuTemperature");
}
}
public double AnimalTemperature
{
get { return _animalTemperature; }
set
{
_animalTemperature = value;
OnPropertyChanged("AnimalTemperature");
}
}
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
public Temperature()
{
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e)
{
MemsTemperature = MemsTemperature + 1;
CpuTemperature = CpuTemperature + 2;
AnimalTemperature = AnimalTemperature + 3;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
MainWindowViewModel
public class MainWindowViewModel
{
public double MemTemp { get; set; }
public MainWindowViewModel()
{
MemTemp = new Temperature().MemsTemperature;
}
}
Main Window Xaml and C# Code
<TextBlock Text="{Binding MemTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel should expose a Temperature property, e.g. like this:
public class MainWindowViewModel
{
public Temperature Temperature { get; } = new Temperature();
}
and the Binding should then look like this:
<TextBlock Text="{Binding Temperature.MemsTemperature}"/>
Neither Mode=TwoWay nor UpdateSourceTrigger=PropertyChanged makes sense on the Binding of a TextBlock's Text property.
The OnPropertyChanged method would simpler and safer be implemented like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You have a XAML page with UI controls that bind to those constantly-changing properties. When you send out the PropertyChanged notifications, the UI control will automatically update itself.
The problem with the code you wrote is that you never bound to the actual temperature. XAML doesn't know how to translate MemTemp into anything other than it's name unless you write a DataTemplate for it.
For example, (assuming a grid) something like this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Animal: "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MemTemp.AnimalTemperature}"/>
I would define an explicit worker class which performs the measurements. This class
has an event (OnMeasurement), which can be subscribed in the ViewModel:
// Arguments for the mesurement event (temperature, ...)
public class MeasurementEventArgs : EventArgs
{
public double Temperature { get; }
public MeasurementEventArgs(double temperature)
{
Temperature = temperature;
}
}
public class MeasurementWorker
{
private readonly CancellationTokenSource _tcs = new CancellationTokenSource();
// Provides an event we can subscribe in the view model.
public event Action<object, MeasurementEventArgs> OnMeasurement;
public void Stop()
{
_tcs.Cancel();
}
// Measurement routine. Perform a measurement every second.
public async Task Start()
{
try
{
var rnd = new Random();
while (!_tcs.IsCancellationRequested)
{
var temperature = 20 * rnd.NextDouble();
OnMeasurement?.Invoke(this, new MeasurementEventArgs(temperature));
await Task.Delay(1000, _tcs.Token);
}
}
catch (TaskCanceledException) { }
// TODO: Create an error event to catch exceptions from here.
catch { }
}
}
In your MainWindow class you instantiate your viewmodel and your worker:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new MeasurementWorker());
}
// Register in XAML with <Window ... Closing="StopMeasurement">
public async void StopMeasurement(object sender, System.ComponentModel.CancelEventArgs e)
{
var vm = DataContext as MainWindowViewModel;
await vm.StopMeasurement();
}
}
In your view model you can subscribe to the worker event and raise OnPropertyChanged in your callback function:
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _memsTemperature;
private readonly MeasurementWorker _mw;
private readonly Task _measurementWorkerTask;
public double MemsTemperature
{
get => _memsTemperature;
set
{
_memsTemperature = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MemsTemperature)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void ProcessMeasurement(object sender, MeasurementEventArgs args)
{
MemsTemperature = args.Temperature;
}
// You can call this if you want to stop your measurement. Should be called if you close your app.
public async Task StopMeasurement()
{
_mw.OnMeasurement -= ProcessMeasurement;
_mw.Stop();
// Clean shutdown
await _measurementWorkerTask;
}
public MainWindowViewModel(MeasurementWorker mw)
{
_mw = mw;
_mw.OnMeasurement += ProcessMeasurement;
_measurementWorkerTask = _mw.Start();
}
}

Binding programmatically to a syncfusion NeedPointer

I am trying to bind without success a property called PointerValue to a NeedlePointer.Value progmatically but seem to have got lost somewhere.
The xamarin app basically has a gauge and a start button when the start button is pressed I start the timer. Upon timer elapsed the needle value should increase by on. Easy in XAML but cant figure out how to convert this to code <gauge:NeedlePointer Value="{Binding PointerValue}"
public class StopWatchPage : BaseContentPage
{
private Timer timer;
private double PointerValue
{
get => (double)GetValue(PointerValueProperty);
set => SetValue(PointerValueProperty, value);
}
private static readonly BindableProperty PointerValueProperty =
BindableProperty.Create("PointerValue",
typeof(double), typeof(StopWatchPage), 0d);
public StopWatchPage()
{
this.BindingContext = this;
var needlePointer = new NeedlePointer
{
Value = PointerValue
};
needlePointer.SetBinding(
PointerValueProperty, nameof(PointerValue));
var scale = new Scale{...};
scale.Pointers.Add(needlePointer);
scales.Add(scale);
circularGauge.Scales = scales;
... add gauge to Content etc...
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
this.PointerValue += 1;
}
}
this should work, although the actual name of ValueProperty might be different depending on how NeedlePointer is implemented. The first argument is the name of the control property that you are binding to (the target), and the second is the name of the value property acts as the source.
needlePointer.SetBinding(NeedlePointer.ValueProperty, "PointerValue");
however, if you want the UI to update dynamically, you will also need to have your BindingContext implement INotifyPropertyChanged
There is no need to create a BindableProperty
Solution thanks to #Jason pointing me to the fact that I needed a model that implements INotifyPropertyChanged so code changed to
public class StopWatchViewModel : INotifyPropertyChanged
{
public double PointerValue { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StopWatchPage : BaseContentPage
{
private Timer timer;
private readonly StopWatchViewModel model = new StopWatchViewModel();
public StopWatchPage()
{
BindingContext = model;
...
needlePointer.SetBinding(NeedlePointer.ValueProperty,
nameof(model.PointerValue));
...
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
model.PointerValue += 1;
}
}

Binding not updated when using IOC

I've never used IOC in any of my apps, and today I decided to learn how to use it but I'm having some issues with bindings.
What happens is that bound values are updated as soon as the page shows, but changing some of them later doesn't reflect to the page.
Here are some snippets of the part that's causing issues:
service
public class TimerService : BindableBase, ITimerService
{
public TimerService()
{
RemainingTime = UpdateInterval;
var updateTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
updateTimer.Tick += UpdateTimerOnTick;
updateTimer.Start();
}
private double _remainingTime;
public int UpdateInterval { get; set; } = 90;
public double RemainingTime
{
get { return _remainingTime; }
set {
// From Template10, it calls RaisePropertyChanged (and even calling it manually doesn't help)
Set(ref _remainingTime, value);
}
}
private void UpdateTimerOnTick(object sender, object o)
{
RemainingTime -= 1;
if (Math.Abs(RemainingTime) < 0.05) RemainingTime = UpdateInterval;
// Timer is running fine as I can see the prints
Debug.WriteLine($"{RemainingTime}");
}
}
locator
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<ITimerService, DesignTimeTimerService>();
}
else
{
SimpleIoc.Default.Register<ITimerService, TimerService>(true);
}
SimpleIoc.Default.Register<MainPageViewModel>();
}
public MainPageViewModel MainPageViewModel => ServiceLocator.Current.GetInstance<MainPageViewModel>();
}
viewModel
public class MainPageViewModel : ViewModelBase
{
public ITimerService TimerService { get; }
public MainPageViewModel(ITimerService timerService)
{
TimerService = timerService;
}
}
page
<Page x:Class="Views.MainPage"
...
DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=MainPageViewModel}"
mc:Ignorable="d">
...
<ProgressBar Maximum="{x:Bind ViewModel.TimerService.UpdateInterval}"
...
Value="{x:Bind ViewModel.TimerService.RemainingTime, Mode=OneWay, Converter={StaticResource EmptyConverter}}" />
...
(.cs file)
private MainPageViewModel ViewModel => DataContext as MainPageViewModel;
What I'd expect is to have ProgressBar's value decreasing as the timer goes, but it just stays still on the very first value that I set.
I've also tried adding an empty converter to see if something happens but it's like ProgressBar never receives the update event.
Do you have any hints on this?

Categories

Resources