Raise PropertyChanged for a property inside a model - c#

So I've got a TimerModel which holds data for the entity and a TimerViewModel which handles the timer ticks from each one of the timers. It simply decrements the TimeSpan set as the interval for the timer - I'm doing this purely because I need to update the View with the remaining time.
public class TimerModel
{
public TimerModel(TimeSpan Interval, TimerViewModel Parent)
{
Timer = new DispatcherTimer();
Timer.Interval = TimeSpan.FromSeconds(1);
parent = Parent;
this.Interval = Interval;
Timer.Tick += (sender, args) => parent._timer_Tick(this, args);
Timer.Start();
}
private TimerViewModel parent;
public DispatcherTimer Timer
{
get;
private set;
}
public TimeSpan Interval
{
get;
set;
}
}
And the TimerViewModel would be:
public class TimerViewModel : ViewModelBase
{
public TimerViewModel()
{
Timers = new ObservableCollection<TimerModel>();
AddTimerCommand = new RelayCommand((object x) => { AddTimer(x); });
}
public ObservableCollection<TimerModel> Timers
{
get;
private set;
}
public ICommand AddTimerCommand
{
get;
private set;
}
private void AddTimer(object s)
{
Timers.Add(new TimerModel(TimeSpan.FromSeconds(250), this));
}
internal void _timer_Tick(object sender, EventArgs e)
{
TimerModel timer = sender as TimerModel;
timer.Interval = timer.Interval.Subtract(TimeSpan.FromSeconds(1));
RaisePropertyChanged("Timers");
}
}
Which is bound to a listbox:
<ListBox x:Name="lst_timers" Height="300" Width="150" HorizontalAlignment="Left"
DataContext="{Binding local:TimerViewModel}" ItemsSource="{Binding Timers}"
DisplayMemberPath="Interval" />
_timer_Tick should be raising the property changed for the whole list, but seemingly the listbox items remain unchanged.
Can anyone give me a clue what am I missing here?

The Timers list is not changing, it's the Interval property of the Timer object that changes.
I'd suggest a ViewModel for your whole view (set as DataContext for your window/control..., not for the ListBox inside this parent window/control) containing a property Timers of type ObervableCollection<TimerViewModel>. This TimerViewModel represents one single timer and should raise a PropertyChanged-Event for "Interval" in the property's setter.
(For additional information see comments)

<ListBox x:Name="lst_timers" ...>
<ListBox.DataContext>
<local:TimerViewModel/>
</ListBox.DataContext>
</ListBox>

Related

How do I get my TextBlock to update text using DataBinding

So I seem to be stuck in a loop (no pun intended).
I created my view which consists of a Button control and a TextBlock control.
I have bound my button to a command which invokes a method from my model.
XAML
<Grid>
<TextBlock Text="{Binding CounterValue}" Width=" 100" Height="20"></TextBlock>
<Button Command="{Binding startCommand}" Content="Button" HorizontalAlignment="Left" Margin="472,230,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
And here is the StartCommand you can ignore this, there is nothing special here
class StartCommand : ICommand
{
private Action _startCommand;
public StartCommand(Action StartCommand)
{
_startCommand = StartCommand;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_startCommand?.Invoke();
}
public event EventHandler CanExecuteChanged;
}
And then we have the model which is a seperate cs file.
class CounterModel
{
static DispatcherTimer calcTimer = new DispatcherTimer();
public StartCommand startCommand { get; } = new StartCommand(Start);
public CounterModel()
{
CounterValue = 10;
}
private static int _counterValue;
public static int CounterValue
{
get { return _counterValue; }
set
{
_counterValue = value;
}
}
public static void Start()
{
//Start some stuff..
Calculate();
}
public static void Calculate()
{
calcTimer.Tick += CalcTimer_Tick;
calcTimer.Interval = TimeSpan.FromSeconds(2);
calcTimer.Start();
}
private static void CalcTimer_Tick(object sender, EventArgs e)
{
PerformanceCounter cpuCounter = new PerformanceCounter
("Process", "% Processor Time", "Firefox", true);
CounterValue = (int)cpuCounter.NextValue();
}
}
My issue right now is that when I click my start button it's not doing anything.. Or well it is but my text property is not updating accoringly, the value is not corresponding to the new value that the timer tick event assigns it.
I tried implementing the interface INotifyPropertyChanged but I cannot do this.
private static int _counterValue;
public static int CounterValue
{
get { return _counterValue; }
set
{
_counterValue = value;
OnPropertyChanged("startCommand");
}
}
Because OnPropertyChanged then needs to be static which again would lead me down a whole new rabbit whole that I shouldnt be down in to begin with.
And I need my properties to be static so I can use them in the Tick event which is called from my Calculate Method which is being called inside Start()
Starts need to be static because I am calling it from alot of others classes.. Either way..
How do I deal with either my properties being static and using INotifyPropertyChanged oooor.. How do I update the TextBlock text value without INotifyPropertyChanged
Without removing the static modifier in Start()
And yes I did set the DataContext
public MainWindow()
{
InitializeComponent();
DataContext = new CounterModel();
}
I understand your scenario here. But isn't the accessibility of your Start() more of a design problem? You can achieve best of both worlds by implementing a singleton pattern to get the value from start method every time you call it, and yet not make Start() static. You can see the example below:
public class CounterModel : INotifyPropertyChanged
{
private static CounterModel _instance = new CounterModel();
public static CounterModel Instance { get { return _instance; } }
private CounterModel()
{
CounterValue = 10;
startCommand = new StartCommand(Start);
}
static DispatcherTimer calcTimer = new DispatcherTimer();
public StartCommand startCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _counterValue;
public int CounterValue
{
get
{
return _counterValue;
}
set
{
_counterValue = value;
NotifyPropertyChanged();
}
}
public void Start()
{
//Start some stuff..
Calculate();
}
public void Calculate()
{
calcTimer.Tick += CalcTimer_Tick;
calcTimer.Interval = TimeSpan.FromSeconds(2);
calcTimer.Start();
}
private void CalcTimer_Tick(object sender, EventArgs e)
{
PerformanceCounter cpuCounter = new PerformanceCounter
("Process", "% Processor Time", "Explorer", true);
CounterValue = (int)cpuCounter.NextValue();
}
}
All the required methods need not be static once the singleton pattern is in place, and you can still always get the common instance of it. Now this is how you can call the instance in your MainWindow.
public MainWindow()
{
InitializeComponent();
DataContext = CounterModel.Instance;
}
In your view you make the following change accordingly,
<TextBlock Text="{Binding CounterValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100" Height="20"></TextBlock>
<Button Command="{Binding startCommand}" Content="Button" HorizontalAlignment="Left" Margin="472,230,0,0" VerticalAlignment="Top" Width="75"/>
The ICommand looked fine so I haven't added it as part of answer. Do let me know if the approach works for you.

How to prevent collapsing of Expander listitem when refresh List with new DataContext in WPF

I made a List of Expanders in WPF. The List is bounded to an array of objects, and I need to update them periodically.
When I update an array of objects by reading them from DB after I expanded one of Expanders in List, the Expander automatically collapse.
Is there a way to prevent that?
Edit 1
This is the part of xml of ObjectTab,
<ListView x:Name="list" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ItemsSource="{Binding Path=Objects}" ScrollViewer.CanContentScroll="False">
<ListView.ItemTemplate>
<DataTemplate>
<local:ObjectRealtimeControl></local:ObjectRealtimeControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and the code of ObjectTab is
public partial class ObjectTab : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SomeObject[] Objects { get; set; }
public ObjectTab ()
{
DataContext = this;
InitializeComponent();
runWorker();
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void runWorker()
{
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += Worker_DoWork;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (!worker.CancellationPending )
{
refreshAll();
Thread.Sleep(1000);
}
}
private void refreshAll()
{
Shared.DB.read("Some SQL", (ex, dataTable) =>
{
Objects = dataTable.AsEnumerable().Select((row)=>{
return new SomeObject() {
id = row[0].toString(),
};
}).ToArray();
OnPropertyChanged("Objects");
}
}
}
ObjectRealtimeControl contains Expander, other controls to represent SomeObject class.
You haven't provided a Minimal, Complete, and Verifiable example of your issue so it's hard to say anything about your expanders but instead of raising the PropertyChanged event for the Objects property itself, you could try to update the individual properties of each object or clear the same collection and re-populare it. Something like this:
public partial class ObjectTab : UserControl
{
public ObservableCollection<SomeObject> Objects { get; set; } = new ObservableCollection<SomeObject>();
public ObjectTab()
{
DataContext = this;
InitializeComponent();
runWorker();
}
...
private void refreshAll()
{
Shared.DB.read("Some SQL", (ex, dataTable) =>
{
var newObjects = dataTable.AsEnumerable().Select((row) => {
return new SomeObject()
{
id = row[0].toString(),
};
}).ToArray();
Dispatcher.BeginInvoke(new Action(() =>
{
Objects.Clear();
foreach (var newObject in newObjects)
Objects.Add(newObject);
}));
};
}
}

DateTime.Now based timer not tracking correctly over multiple instances

this is my first proper C# application that I wrote to help me at work (I'm on helpdesk for an MSP with a passing interest in scripting and code) and I'm using UWP just to make it look pretty without much effort. Our time tracking software is a web service written in ASP.Net so generally the built in timer is fine but it won't survive a browser refresh, so I wrote my own that fits into the format that we need for our tickets.
I have taken some code from other Stack questions and my dad (A C# framework dev for a multinational) helped re-write some of the timer code so it wasn't using stopwatch. He just isn't available to fix this issue at the moment. I do understand how it works now, just not how to debug the issue I'm getting.
It supports multiple timers running at the same time and creating a new timer auto-pauses all others. It handles two time formats, minutes and decimal hours, so that will explain some of the properties you see in the code.
My issue is that when I add a new timer, it pauses all others, but then when I press start on an older timer (Returning back to an earlier ticket) the time instantly jumps up to how long the new timer was running for, with about 10% difference (It's never exactly how long it was running).
This is the class that tracks notes and the current time (Tidied up a bit for neatness):
public sealed class JobTimer:INotifyPropertyChanged
{
private DateTime _created; // When the timer was created
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_created = DateTime.Now;
IsNotLocked = true;
}
// Time in seconds
public string TimeMin => string.Format("{0:00}:{1:00}:{2:00}", ElapsedTime.Hours, ElapsedTime.Minutes, ElapsedTime.Seconds);
// Time in decimal hours
public string TimeDec => string.Format("{0}", 0.1 * Math.Ceiling(10 * ElapsedTime.TotalHours));
public DateTime Created => _created;
public TimeSpan ElapsedTime => GetElapsed();
public void Start()
{
_started = DateTime.Now;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
NotifyPropertyChanged("IsRunning");
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset = _offset.Add(DateTime.Now.Subtract(_started));
NotifyPropertyChanged("IsRunning");
}
private TimeSpan GetElapsed()
{
// This was made as part of my own debugging, the ElaspsedTime property used to just be the if return
if (IsRunning)
{
return _offset.Add(DateTime.Now.Subtract(_started));
}
else
{
return _offset;
}
}
// Updates the UI
private void TimerChanged(object state)
{
NotifyPropertyChanged("TimeDec");
NotifyPropertyChanged("TimeMin");
}
public bool IsRunning
{
get { return _swTimer != null; }
}
public void ToggleRunning()
{
if (IsRunning)
{
Stop();
}
else
{
Start();
}
}
}
This goes into the ViewModel:
public class JobListViewModel
{
private readonly ObservableCollection<JobTimer> _list = new ObservableCollection<JobTimer>();
public ObservableCollection<JobTimer> JobTimers => _list;
public JobListViewModel()
{
AddTimer();
}
public void AddTimer()
{
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
public void PauseAll()
{
foreach(JobTimer timer in JobTimers)
{
timer.Stop();
}
}
// Other functions unrelated
}
And this is the UI button click that adds a new timer
private void AddTimer_Click(object sender, RoutedEventArgs e)
{
// Create JobTimer
ViewModel.PauseAll();
ViewModel.AddTimer();
// Scroll to newly created timer
JobTimer lastTimer = ViewModel.JobTimers.Last();
viewTimers.UpdateLayout();
viewTimers.ScrollIntoView(lastTimer);
}
I realise it's a lot of code to dump into a post but I can't pinpoint where the issue is being caused. I was able to find that something alters the offset when I hit the AddTimer button whether the existing timer is running or not, but I can't find what's altering it.
After building enough other code to support the code you posted, I was able to reproduce your problem.
The issue in your code is that you unconditionally call the Stop() method, whether the timer is already stopped or not. And the Stop() method unconditionally resets the _offset field, whether or not the timer is already running. So, if you add a timer when any other timer is already stopped, its _offset value is incorrectly reset.
IMHO, the right fix is for the Start() and Stop() methods to only perform their work when the timer is in the appropriate state to be started or stopped. I.e. to check the IsRunning property before actually doing the operation.
See below for an actual Minimal, Complete, and Verifiable version of the code you posted, but without the bug.
In addition to fixing the bug, I removed all of the unused elements (that is, all the code that did not appear to be used or discussed in your scenario) and refactored the code so that it is more idiomatic of a typical WPF implementation (see helper/base classes at the end). When I run the program, I am able to start and stop the timer objects without any trouble, even after adding new timers to the list.
Notable modifications:
Use of NotifyPropertyChangedBase class as base class for model classes.
Leverage of said base class features for property change notification, by keeping public properties as simple value-storing properties modified as needed.
Use of ICommand implementation for user actions (i.e. "commands").
Separation of timer-specific start/stop functionality when adding timers from view-specific scrolling-into-view behavior.
Remove time-formatting logic from non-UI model object, and put it in the XAML instead
Use conventional (and more readable) - and + operators for DateTime and TimeSpan math
JobTimer.cs:
class JobTimer : NotifyPropertyChangedBase
{
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
private readonly DelegateCommand _startCommand;
private readonly DelegateCommand _stopCommand;
public ICommand StartCommand => _startCommand;
public ICommand StopCommand => _stopCommand;
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_startCommand = new DelegateCommand(Start, () => !IsRunning);
_stopCommand = new DelegateCommand(Stop, () => IsRunning);
}
private TimeSpan _elapsedTime;
public TimeSpan ElapsedTime
{
get { return _elapsedTime; }
set { _UpdateField(ref _elapsedTime, value); }
}
public void Start()
{
_started = DateTime.UtcNow;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
IsRunning = true;
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset += DateTime.UtcNow - _started;
IsRunning = false;
}
private TimeSpan GetElapsed()
{
return IsRunning ? DateTime.UtcNow - _started + _offset : _offset;
}
// Updates the UI
private void TimerChanged(object state)
{
ElapsedTime = GetElapsed();
}
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set { _UpdateField(ref _isRunning, value, _OnIsRunningChanged); }
}
private void _OnIsRunningChanged(bool obj)
{
_startCommand.RaiseCanExecuteChanged();
_stopCommand.RaiseCanExecuteChanged();
}
}
MainViewModel.cs:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<JobTimer> JobTimers { get; } = new ObservableCollection<JobTimer>();
public ICommand AddTimerCommand { get; }
public MainViewModel()
{
AddTimerCommand = new DelegateCommand(_AddTimer);
_AddTimer();
}
private void _AddTimer()
{
foreach (JobTimer timer in JobTimers)
{
timer.Stop();
}
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel model = (MainViewModel)DataContext;
model.JobTimers.CollectionChanged += _OnJobTimersCollectionChanged;
}
private void _OnJobTimersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<JobTimer> jobTimers = (ObservableCollection<JobTimer>)sender;
// Scroll to newly created timer
JobTimer lastTimer = jobTimers.Last();
listBox1.ScrollIntoView(lastTimer);
}
}
MainWindow.xaml:
<Window x:Class="TestSO46416275DateTimeTimer.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:l="clr-namespace:TestSO46416275DateTimeTimer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:JobTimer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElapsedTime, StringFormat=hh\\:mm\\:ss}"/>
<Button Content="Start" Command="{Binding StartCommand}"/>
<Button Content="Stop" Command="{Binding StopCommand}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add Timer" Command="{Binding AddTimerCommand}" HorizontalAlignment="Left"/>
<ListBox x:Name="listBox1" ItemsSource="{Binding JobTimers}" Grid.Row="1"/>
</Grid>
</Window>
NotifyPropertyChangedBase.cs:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
DelegateCommand.cs:
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null)
{ }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}

Binding to property not working with DispatcherTimer

I have some TextBlock bound to a property with DependencyProperty. When a DispatcherTimer changes this property, the TextBlock does not update. Even in debugging, I can see that the property get updated, but TextBlock remains unchanged.
Details:
I have a class:
public class myTimer
{
public System.DateTime Duration { get; set; }
public System.DateTime Elapsed { get; set; }
public System.TimeSpan Remaining {
get {
return Duration.Subtract(new DateTime(Duration.Year, Duration.Month, Duration.Day, Elapsed.Hour, Elapsed.Minute, Elapsed.Second));
}
}
}
I have in my xaml code behind a DependencyProperty of type myTimer
public static DependencyProperty currentTimerProperty = DependencyProperty.Register("CurrentTimer", typeof(myTimer), typeof(Question));
public myTimer CurrentTimer
{
get { return (myTimer)GetValue(currentTimerProperty); }
set { SetValue(currentTimerProperty, value); }
}
and I have three TextBlock bounded to this property:
<TextBlock Style="{StaticResource myTimer}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0:00}:{1:00;00}">
<Binding ElementName="Questionctl" Path="CurrentTimer.Remaining.Minutes"/>
<Binding ElementName="Questionctl" Path="CurrentTimer.Remaining.Seconds"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ElementName=Questionctl,Path=CurrentTimer.Duration,StringFormat=HH:mm:ss}"/>
<TextBlock Text="{Binding ElementName=Questionctl,Path=CurrentTimer.Elapsed,StringFormat=HH:mm:ss}"/>
the timer is initialized like this:
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan( 0 , 0, 1);
dispatcherTimer.Start();
so simply, every second, it will add 1 second to the property Elapsed:
CurrentTimer.Elapsed = CurrentTimer.Elapsed.AddSeconds(1);
Update your Class definition of myTimer to implement INotifyPropertyChanged looks like:
public class myTimer : INotifyPropertyChanged
{
private System.DateTime _duration;
public System.DateTime Duration
{
get
{
return _duration;
}
set
{
_duration = value;
RaisePropertyChanged("Duration");
RaisePropertyChanged("Remaining");
}
}
private DateTime _elapsed;
public DateTime Elapsed
{
get { return _elapsed; }
set
{
_elapsed = value;
RaisePropertyChanged("Elapsed");
RaisePropertyChanged("Remaining");
}
}
public System.TimeSpan Remaining
{
get
{
return Duration.Subtract(new DateTime(Duration.Year, Duration.Month, Duration.Day, Elapsed.Hour, Elapsed.Minute, Elapsed.Second));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If that is your actual problem and not a simplification, you'll have another issue.
Your code will only work if, and only if, the dispatch timer event happens exactly on one second intervals. That is not guaranteed by that api.
If you instead capture the system time when the timer fires it will calculate the correct times even if the time between event firing is sporadic.

Questions about codes in the setter of a DependencyProperty in a user control

I am making a display of time/clock as a user control (ClockControl) on a page, the actual time model is driven from a DateTime object from another class (ClockTime). I have 2 textblocks on the ClockControl:
Textblock 'Second_update_by_binding' is bound to a dependency property 'Second_binded' which in turn is bound to model ClockTime 'Second'.
Textblock 'Second_update_by_manipulating' is updated by manipulating the value of the model ClockTime 'Second' so that it adds '0' at front if the 'Second' is only 1 digit (or less than 10)
I managed to achieve what I want regardless if its the best approach. However, I have come across a few questions that I don't quite understand why they happen. In particular, its the logic behind the code inside the getter/setter within the dependency property in the Clock user control that I am most confused of.
MainPage.xaml:
<Page x:Class="App1.MainPage" ...">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:ClockControl x:Name="useControl"
Second_binded="{Binding Second}"
Second_manipulated="{Binding Second}" />
</Grid>
</Page>
MainPage.cs:
public sealed partial class MainPage : Page
{
ClockTime clock;
DispatcherTimer Timer;
public MainPage()
{
clock = new ClockTime();
this.InitializeComponent();
this.DataContext = clock;
// I am adding this DispatchTimer to force the update of the text
// 'Second_update_by_manipulating' on the ClockControl since the
// Binding of Second_manipulated doesnt work
Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
useControl.Second_manipulated = clock.Second.ToString();
}
}
ClockTime model:
class ClockTime : INotifyPropertyChanged
{
public ClockTime()
{
var Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
Second = DateTime.Now.Second;
}
//===================================================
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
//===================================================
private int _second;
public int Second
{
get { return this._second; }
set
{
if (value != this._second)
{
this._second = value;
NotifyPropertyChanged("Second");
}
}
}
}
ClockControl.xaml:
<UserControl x:Class="App1.ClockControl" ...>
<Grid>
<StackPanel>
<TextBlock x:Name="Second_update_by_manipulating" />
<TextBlock x:Name="Second_update_by_binding" Text="{Binding Second_binded}" />
</StackPanel>
</Grid>
</UserControl>
ClockControl.cs:
public sealed partial class ClockControl : UserControl
{
public ClockControl()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
//==================================
public String Second_binded
{
get {
return (String)GetValue(Second_bindedProperty);
}
set {
Debug.WriteLine(" in Second_binded ");
SetValue(Second_bindedProperty, value);
}
}
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
public String Second_manipulated
{
get
{
return (String)GetValue(Second_manipulatedProperty);
}
set
{
Second_update_by_manipulating.Text = (Convert.ToInt32(value)<10) ? "0"+value : value;
Debug.WriteLine(" in Second_manipulated ");
SetValue(Second_manipulatedProperty, value);
}
}
public static readonly DependencyProperty Second_manipulatedProperty =
DependencyProperty.Register(
"Second_manipulated",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
}
so here are my questions:
Why the debugging code Debug.WriteLine(" in Second_binded "); within the setter of Second_binded dependency property in ClockControl is never called when the 'Second' in model ClockTime is being updated.
The code Debug.WriteLine(" in Second_manipulated "); within the setter of Second_manipulated dependency property in ClockControl is called and the code Value_1.Text = (Convert.ToInt32(value)<10) ? "0"+value : value; is executed to change the text on the ClockControl, but it only works after adding another DispatchTimer withtin the MainPage.cs to force the code useControl.Second_manipulated = clock.Second.ToString(); to update the time. Why I have to do it this way to update Second_manipulated, even though I have already set Second_manipulated binded to Second in the MainPage.xaml?
Any ideas and comments to enlighten my knowledge on C# is very welcomed.
thanks
Ken
If you want to track changes in a DependencyProperty, you have to register a PropertyChangedCallback handler. The property setter is not triggered by the system when the value of a binding is updated.
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata("", PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debug.WriteLine(" in Second_binded callback");
}
The reason why your setter is hit in the second property is because you force it yourself with useControl.Second_manipulated = clock.Second.ToString();, which is not the correct way of using bindings.

Categories