I am trying to start by looking at a simple clock example.
i am wondering how to I convert a normal clock to MVVM.
Can someone help?
Thanks.
private void startClock()
{
currentTime = DateTime.Now;
DispatcherTimer clocktimer = new DispatcherTimer();
clocktimer.Interval = TimeSpan.FromSeconds(1);
clocktimer.Tick += Clocktimer_Tick;
clocktimer.Start();
}
private void Clocktimer_Tick(object sender, object e)
{
currentTime = DateTime.Now;
updateTimeDisplay(currentTime);
}
private void updateTimeDisplay(DateTime time)
{
Time.Text = time.ToString(#"HH\:mm");
Sec.Text = time.ToString(#"ss");
Date.Text = time.ToString(#"ddd dd-MM-yyyy");
}
Update
I can't post more codes as the system flagged and unable to post.
UWP C# MVVM clock example
You could make string property in the viewmode and bind it with your textblock in the xaml. When you update the this property, it will notify ui update.
For example:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
startClock();
}
private DateTime _currentTime;
private void startClock()
{
_currentTime = DateTime.Now;
DispatcherTimer clocktimer = new DispatcherTimer();
clocktimer.Interval = TimeSpan.FromSeconds(1);
clocktimer.Tick += Clocktimer_Tick;
clocktimer.Start();
}
private void Clocktimer_Tick(object sender, object e)
{
_currentTime = DateTime.Now;
updateTimeDisplay(_currentTime);
}
private void updateTimeDisplay(DateTime time)
{
Time = time.ToString(#"HH:mm:ss");
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _time;
public string Time
{
get
{
return _time;
}
set
{
_time = value;
this.OnPropertyChanged();
}
}
}
Xaml code
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="Time"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="25"
Text="{Binding Time}"
TextAlignment="Center" />
</Grid>
For detail mvvm design, please refer data binding in depth document.
View model will be something like this:
public class ClockViewModel : ViewModelBase
{
public ClockViewModel()
{
var _ = UpdateCurrentDateTimeAsync();
}
public DateTime? CurrentDateTime { get; private set; }
private async Task UpdateCurrentDateTimeAsync()
{
while (true)
{
CurrentDateTime = DateTime.Now;
OnPropertyChanged(nameof(CurrentDateTime));
await Task.Delay(1000);
}
}
}
Just set it as the DataContext of your view and bind some visual (e.g. TextBox) to CurrentDateTime property. ViewModelBase is a very basic INotifyPropertyChanged implementation.
Related
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>
I am making a fitness app so I need a timer.I have done this in code-behind:
public partial class Timer : ContentView
{
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--;
}
public string Time
{
get => seconds.ToString();
}
}
And then for the UI, I made a label and bound its text property to my Time property:
<Label BindingContext ="{x:Reference this}"
Text="{Binding Time}"/>
//"this" is a reference to my class
When I start the app, the timer remains 30. I know that "seconds" is for sure decreasing, so there must be a problem with the binding.I know I could've just updated the text property of the label inside OneSecondPassed , but I'd like to learn more about data binding.Help?
As Jason said, implementing the INotifyPropertyChanged interface is a good choice.
For your better understanding, I wrote a runnable project that meets your requirements for your reference.
Here is the xaml code:
<StackLayout>
<Label Text="{Binding DateTime}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</StackLayout>
Here is the cs code:
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
int dateTime;
public event PropertyChangedEventHandler PropertyChanged;
public MainPage()
{
InitializeComponent();
this.DateTime = 30;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
if (DateTime > 0)
{
DateTime--;
}
return true;
});
BindingContext = this;
}
public int DateTime
{
set
{
if (dateTime != value)
{
dateTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}
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();
}
}
I am a student that just finished up a summer internship, and I brought home a project to work on briefly before school starts up. This project has a stopwatch in it, and I would rather use an ObservableCollection bound to my ListBox for my split times, rather that using the listbox.Items.Add(). When I add to the ObservableCollection, the ListBox UI does not update. Could anyone point me in the right direction on what I missed or what I did wrong?
I have my TimeSplits class:
public class TimeSplits : INotifyPropertyChanged
{
private int _hours;
private int _minutes;
private int _seconds;
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged(hours);
}
}
public int minutes
{
get
{
return _minutes;
}
set
{
_minutes = value;
NotifyPropertyChanged(minutes);
}
}
public int seconds
{
get
{
return _seconds;
}
set
{
_seconds = value;
NotifyPropertyChanged(seconds);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(int propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
}
}
public override string ToString()
{
return hours.ToString() + ":" + minutes.ToString() + ":" + seconds.ToString();
}
}
and my ObservableCollection in my Page:
public partial class StopwatchPage : Page , INotifyPropertyChanged
{
...
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
...
public StopwatchPage()
{
DataContext = this;
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(stopwatchTimer);
}
...
private void splitButton_Click(object sender, RoutedEventArgs e)
{
TimeSplits split = new TimeSplits();
split.hours = Hours;
split.minutes = Minutes;
split.seconds = Seconds;
splits.Add(split);
}
...
}
and my xaml:
<ListBox x:Name="newSplitListBox" HorizontalAlignment="Left" Margin="139,0,0,47" Width="185" Height="268" VerticalAlignment="Bottom" ItemsSource="{Binding splits}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding hours}"/>
<TextBlock Text="{Binding minutes}"/>
<TextBlock Text="{Binding seconds}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am sure it is something small that I have no clue about, as I just started learning data binding this summer. Any help is greatly appreciated! Thanks in advance.
It looks like you have nameof() in the wrong place. The way your current code reads, it will always send the value of "propertyName" as the name of the property that changed, regardless of what property actually changed.
Try this:
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged();
}
}
Then, in your NotifyPropertyChanged(), do this:
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
Edit: Added fix for the following:
Also, the ObservableCollection needs to be a property. Change this code:
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
To this:
public ObservableCollection<TimeSplits> Splits { get; set; } = new ObservableCollection<TimeSplits>();
I learned a trick from Xamarin's ViewModel template that helped me immensely. Here is the code that it generates that handles an observable View Model (much like the ObservableCollection).
protected bool SetProperty<T>(ref T backingStore, T value,
Action onChanged = null,
[CallerMemberName]string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Then, to use this, simply add this to your properties:
private string _title = string.Empty;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
I convert my application from WP 8.1 / W 8.1 to UWP. It includes a timer updating the value of a textbox. Here is the XAML :
Text="{Binding CurrentLocalDateTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}"
And the data context :
private DateTime currentLocalDateTime;
public DateTime CurrentLocalDateTime { get { return currentLocalDateTime; } set { currentLocalDateTime = value; OnPropertyChanged("CurrentLocalDateTime"); } }
private void TimerExecution(object sender, object e)
{
CurrentLocalDateTime = DateTime.Now;
}
I'd like to use the new x:Bind bining way, but the control is never updated, with this code :
Text="{x:Bind CurrentLocalDateTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}"
Data context :
public DateTime CurrentLocalDateTime { get; set; }
private void TimerExecution(object sender, object e)
{
CurrentLocalDateTime = DateTime.Now;
}
What's wrong ?
Thank you very much for your help.
Regards
When you set CurrentLocalDateTime, there's nothing to notify your UI that it happened. It works in the first case because you're implementing INotifyPropertyChanged and calling OnPropertyChanged with property name.
public DateTime CurrentLocalDateTime
{
get { return currentLocalDateTime; }
set
{
currentLocalDateTime = value;
OnPropertyChanged("CurrentLocalDateTime");
}
}