I'm gonna write a code with DataBinding and a Timer to change an Image sequentially.
e.g: in each two seconds.
below is my C# code :
public class GenerateRandomImagePath
{
Random random = new Random((int)DateTime.Now.Ticks);
readonly int MinInt;
readonly int MaxInt;
readonly string PrefixImagesName;
readonly string ImageExtension;
/// <summary>
/// Used in data binding
/// </summary>
public string ImageFullPath { get; set; }
public GenerateRandomImagePath(string prefixName, string extension)
{
this.PrefixImagesName = prefixName;
this.MinInt = 1;
this.MaxInt = 100;
this.ImageExtension = extension;
}
int RandomNumber()
{
return random.Next(this.MinInt, this.MaxInt);
}
public void GenerateNewRandomImagePath()
{
this.ImageFullPath = this.PrefixImagesName + RandomNumber() + this.ImageExtension;
}
}
public partial class MainWindow : Window
{
GenerateRandomImagePath RandomImagePath;
System.Timers.Timer timer = new System.Timers.Timer(2000);
public MainWindow()
{
InitializeComponent();
RandomImagePath = new GenerateRandomImagePath(#"C:\Users\MDS\Pictures\Nature 02\Nature ", #".jpg");
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Enabled = true;
this.DataContext = RandomImagePath;
RandomImagePath.GenerateNewRandomImagePath();//this line works well
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
RandomImagePath.GenerateNewRandomImagePath();
}
}
The XAML code :
<Window
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"
x:Class="sth.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="800" Height="600" mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Image Source="{Binding Path=ImageFullPath}" />
</Grid>
</Window>
The code works just for first time! After that, the timer changes ImageSource but it doesn't effect on view!
Would you please guide me?
Thanks
According to your code, the GenerateRandomImagePath class doesn't implement INotifyPropertyChanged. WPF can't know that the ImageFullPath has changed unless you tell it, either by implementing that interface or by changing the class to derive from DependencyObject and turning the property into a dependency property.
I would suggest implementing INotifyPropertyChanged - it's a more lightweight approach.
public class GenerateRandomImagePath : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
private string _imageFullPath;
public string ImageFullPath
{
get { return _imageFullPath; }
set
{
_imageFullPath = value;
OnPropertyChanged("ImageFullPath");
}
}
}
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>
EDIT: I have updated this with the two methods recommended
I am writing a simple custom PI (OSISoft) data viewer. I have two classes, one for the UI and one for the PI server interactions/program logic. The property for the data to be displayed has an event that fires when the property is changed. How do I get that change to propagate over to the UI class so the associated text box will automatically refresh?
Original code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue(object sender, TextChangedEventArgs e) {
// ??? something happens here?
}
}
public class ProgLogic {
private int someValue;
public event System.EventHandler SomeValueChanged;
protected void OnSomeValueChanged()
{
SomeValueChanged?.Invoke(this, EventHandlerArgs e);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged();
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 1: Events
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += Logic_SomeValueChanged;
InitializeValues();
}
private void Logic_SomeValueChanged(int obj) {
TextBoxSomeValue.Text = obj.toString();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
logic.SomeValueChanged -= Logic_SomeValueChanged;
}
}
public class ProgLogic {
private int someValue;
public event Action<int> SomeValueChanged;
public virtual void OnSomeValueChanged(int newValue) {
SomeValueChanged?.Invoke(newValue);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged(value);
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 2: MVVM pattern
MainWindow.xaml:
<Window
Closing="Window_Closing"
Title="My App">
<TextBox x:name="TextBoxSomeValue" text="{binding SomeValue, UpdateSourceTrigger=PropertyChanged}" />
</Window>
The important part here is the binding parameter in the text field of the TextBox definition, which points to the PropertyChangedEventHandler.
C# code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
// run some other code when the text box updates
}
}
public class ProgLogic : INotifyPropertyChanged {
private int someValue;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnPropertyChange("SomeValue")
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
ProgLogic now implements INotifyPropertyChanged, which notifies the View of property changes, so that Bindings are updated.
I see you are heading the right way with C# event system. One thing I would change is event type from System.EventHandler to System.Action<int>. Even though people tend to propagate extending System.EventArgs class and writing custom delegates for handling events, using System.Action<T> is much easier to grasp for beginner.
So let's go with System.Action<int> example now. First, let's change ProgLogic class to be more like this:
public class ProgLogic
{
public event Action<int> SomeValueChanged;
//
// your other code goes here
//
private void OnSomeValueChanged(int newValue)
{
SomeValueChanged?.Invoke(newValue);
}
}
Now, you need to subscribe to the earlier written event in MainWindow class. So we do that as early as possible - in the constructor of MainWindow:
public MainWindow()
{
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += OnSomeValueChanged;
InitializeValues();
}
Then, you describe your logic in the OnSomeValueChanged callback method, like:
private void OnSomeValueChanged(int newValue)
{
TextBoxSomeValue.text = newValue.ToString();
}
Make sure you unsubscribe from the event once MainWindow is getting destroyed to prevent memory leakage. This is just bare-bones for whole logic. I've left some space for interpretation. ;)
I'm not sure if I'm understanding the main point of your question but if you want to create a new value and have that value saved as the default value then you should create a string in your application setting and call on it on text changed.
At the top of your visual2019, in the menu options. open the debug menu and at the bottom you will see ("Your project name" + properties)
2.You will be brought into a new window with menu options on the left, go to the settings.
3.Create a string and set the value to "Some random text"
Note: In the example I placed one text box in front of the other, though this in not a great method it will prevent the text from appearing as a double or drawing a blank
Settings String Example
xaml
<Window x:Class="SaveNewText.MainWindow"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox x:Name="DefaultText" Height="250" Width="250"
Background="Transparent"
Foreground="Black" MouseDown="TextBlock_MouseDown" IsReadOnly="True"/>
<TextBox x:Name="NewText" Height="250" Width="250" Background="Transparent"
Foreground="Black" TextChanged="NewText_TextChanged"/>
</Grid>
</Window>
xaml.cs
namespace SaveNewText
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DefaultText.Text = Properties.Settings.Default.TextString;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
NewText.Focus();
}
private void NewText_TextChanged(object sender, TextChangedEventArgs e)
{
Properties.Settings.Default.TextString = NewText.Text;
Properties.Settings.Default.Save();
DefaultText.Text = Properties.Settings.Default.TextString;
}
}
}
I've created a WPF project called WpfDataBindTests. With the MainWindow.xaml I've got this setup:
<Window x:Class="WpfDataBindTests.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:WpfDataBindTests"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Name="myGrid">
<TextBox Text="{Binding Path=Name2}"/>
</Grid>
</Window>
In the .xaml.xs I've got this
namespace WpfDataBindTests
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
myGrid.DataContext = this;
_name2 = "Start...";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name2;
public string Name2
{
get { return _name2; }
set
{
if (value != _name2)
{
_name2 = value;
OnPropertyChanged("Name2");
}
}
}
}
}
I've then changed the app type to a console app and removed the App.xaml to redefine the Program class as the entry point:
namespace WpfDataBindTests
{
class Program
{
[STAThread]
static void Main(string[] args)
{
MainWindow x = new MainWindow();
x.Show();
x.Name2 = "HELLO";
System.Threading.Thread.Sleep(1000);
x.Name2 = "THIS";
System.Threading.Thread.Sleep(1000);
x.Name2 = "WORKED";
Console.ReadKey();
}
}
}
My issue is that calling the x.Name2 = "xyz" lines do not change the text shown on the WPF window.
So far I've tried adding to the TextBox Binding paramter:
RelativeSource={RelativeSource AncestorType=Window}
and
Mode=TwoWay, neither of which makes a difference.
But if I add MessageBox.Show("") in between the x.Name2 = "" lines, the text on the form changes!!! - So what's going on here that forces the refresh?
class Program
{
[STAThread]
static void Main(string[] args)
{
MainWindow x = new MainWindow();
x.Show();
x.Name2 = "HELLO";
MessageBox.Show("");
System.Threading.Thread.Sleep(1000);
x.Name2 = "THIS";
MessageBox.Show("");
System.Threading.Thread.Sleep(1000);
x.Name2 = "WORKED";
MessageBox.Show("");
Console.ReadKey();
}
}
I've then changed the app type to a console app and removed the App.xaml to redefine the Program class as the entry point ..
Why did you do this? If you define your own custom Main method, you should create an instance of the Application class and call its Run method. This attaches a new Dispatcher instance to the UI thread, and then the Dispatcher object's Run method is called. This starts a message pump to process windows messages. You will need this to be able to see the updates:
class Program
{
[STAThread]
static void Main(string[] args)
{
Application app = new Application();
app.Run(new MainWindow());
}
}
The Run method won't return until you shutdown the application.
If you use the default WPF Application template in Visual Studio and for example handle the Loaded event for your window, you should see the value being updated, e.g.:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
myGrid.DataContext = this;
_name2 = "Start...";
Loaded += MainWindow_Loaded;
}
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//wait for 2 seconds...
await Task.Delay(2000);
//...and then set the property
Name2 = "new...";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name2;
public string Name2
{
get { return _name2; }
set
{
if (value != _name2)
{
_name2 = value;
OnPropertyChanged("Name2");
}
}
}
}
I've found a fix for my issue, I've gotten rid of DataBinding and just named my TextBox as "TBox" although I don't think that part was necessary, but I did remove everything in MainWindow including INotifyPropertyChanged
So my MainWindow.xaml.cs now looks like this and it works fine and dandy:
private delegate void NoArgDelegate();
private static void Refresh(DependencyObject obj)
{
obj.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle,
(NoArgDelegate)delegate { });
}
public void LogoUpdate(MainWindow w, string m)
{
w.TBox.Text = m;
Refresh(w);
}
Trying to make my first application with the simple logging function to the TextBox on main form.
To implement logging, I need to get the TextBox object into the logger's class.
Prob - can't do that :) currently have no error, but as I understand the text value of TextBox is binding to my ViewModel, because getting 'null reference' exception trying to execute.
Logger.cs
public class Logger : TextWriter
{
TextBox textBox = ViewModel.LogBox;
public override void Write(char value)
{
base.Write(value);
textBox.Dispatcher.BeginInvoke(new Action(() =>
{
textBox.AppendText(value.ToString());
}));
}
public override Encoding Encoding
{
get { return System.Text.Encoding.UTF8; }
}
}
ViewModel.cs
public class ViewModel
{
public int ThreadCount { get; set; }
public int ProxyTimeout { get; set; }
public static TextBox LogBox { get; set; }
//private TextBox _LogBox;
//public TextBox LogBox {
// get { return _LogBox; }
// set {
// _LogBox = value;
// }
//}
}
launching on btn click, MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Logger logger = new Logger();
logger.Write("ewgewgweg");
}
}
MainWindow.xaml
<Window
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:tools"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" x:Class="tools.MainWindow"
mc:Ignorable="d"
Title="Tools" Height="399.387" Width="575.46">
<TextBox x:Name="logBox"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto" HorizontalAlignment="Left" Height="137" Margin="10,222,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="394" Text="{Binding Path = LogBox, Mode=TwoWay}"/>
You have several issues in your code:
Don't bring controls (TextBox) in your viewmodel, if you do there's no use in trying to do MVVM.
The Text property in XAML has to be of the type String or something that can be converted to a string. You're binding a control, which will result in showing System.Windows.Controls.TextBox (result of .ToString()) on your screen instead of actual text.
Your LogBox property should implement INotifyPropertyChanged
You don't want TwoWay binding, as the text flows from your logger to the UI, you don't need it to flow back. You might even consider using a TextBlock instead or make the control readonly so people can't change the content.
You don't want static properties or static viewmodels, read up on dependency injection on how to pass dependencies.
You will be flooding your UI thread by appending your characters one by one. Consider using another implementation (but I won't go deeper into this for this answer).
Keeping all above in mind, I transformed your code to this.
MainWindow.xaml
<TextBox x:Name="logBox"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="137" Margin="10,222,0,0"
TextWrapping="Wrap" Width="394" Text="{Binding Path = LogBox}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private Logger _logger;
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
DataContext = viewModel;
_logger = new Logger(viewModel); // passing ViewModel through Dependency Injection
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_logger.Write("ewgewgweg");
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public int ThreadCount { get; set; }
public int ProxyTimeout { get; set; }
private string _logBox;
public string LogBox
{
get { return _logBox; }
set
{
_logBox = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Logger.cs
public class Logger : TextWriter
{
private readonly ViewModel _viewModel;
public Logger(ViewModel viewModel)
{
_viewModel = viewModel;
}
public override void Write(char value)
{
base.Write(value);
_viewModel.LogBox += value;
}
public override Encoding Encoding
{
get { return System.Text.Encoding.UTF8; }
}
}
You can use string instead of TextBox as follow as
In view model class
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _logBox;
public string LogBox
{
get {return _logBox;}
set
{
if(value != _logBox)
{
_logBox=value;
OnPropertyChanged("LogBox");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
and in writer method you just
public void writer (string str)
{
ViewModel.LogBox = str;
}
You can define ViewModel as static or create new object from ViewModel and access the object in logger class as you want!
hope this helped.
I am doing a program, which must have a clock hanging every second, main problem is that I am beginner in WPF and MVVM :)
But otherwise my clock is running just not refreshing. I have special class for only Time and Date purpose.
Here is my code:
TimeDate class:
public class TimeDate : ViewModelBase
{
public string SystemTimeHours;
string SystemTimeLong;
string SystemDateLong;
string SystemTimeZoneLong;
string SystemTimeShort;
string SystemDateShort;
string SystemTimeZoneShort;
public void InitializeTimer()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
SystemTimeHours = DateTime.Now.ToString("HH:mm tt");
}
}
ViewModel:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
TimeDate td = new TimeDate();
td.InitializeTimer();
HoursTextBox = td.SystemTimeHours;
}
private string _hourstextbox;
public string HoursTextBox
{
get
{ return _hourstextbox; }
set
{
if (value != _hourstextbox)
{
_hourstextbox = value;
NotifyPropertyChanged("HoursTextBox");
}
}
}
}
And also NotifyProperty in ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ViewModelBase()
{
}
#endregion // Constructor
#region DisplayName
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void NotifyPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
protected virtual void NotifyPropertyChangedAll(object inOjbect)
{
foreach (PropertyInfo pi in inOjbect.GetType().GetProperties())
{
NotifyPropertyChanged(pi.Name);
}
}
public virtual void Refresh()
{
NotifyPropertyChangedAll(this);
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
/// </summary>
~ViewModelBase()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
System.Diagnostics.Debug.WriteLine(msg);
}
#endregion // IDisposable Members
}
What to do, that Clock will refresh every second? Please help
As MSDN:
Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that the DispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.
I make shorter example:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="98" Width="128"
xmlns:local="clr-namespace:WpfApplication1">
<Window.DataContext>
<!-- This will auto create an instance of ViewModel -->
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Name="lblTimer" Grid.Row="1" Content="{Binding Path=CurrentTime}"></Label>
</Grid>
</Window>
CS:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ViewModel : ViewModelBase
{
private string _currentTime;
public DispatcherTimer _timer;
public string CurrentTime
{
get
{
return this._currentTime;
}
set
{
if (_currentTime == value)
return;
_currentTime = value;
OnPropertyChanged("CurrentTime");
}
}
public ViewModel()
{
_timer = new DispatcherTimer(DispatcherPriority.Render);
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += (sender, args) =>
{
CurrentTime = DateTime.Now.ToLongTimeString();
};
_timer.Start();
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Hope this help.
You have to specify the DispatcherTimer as a field in your viewmodel class, not just as a local variable in ctor.
Garbage Collector will destroy all local variables when they are out of scope.
Here is my implementation of the clock :)
public class MainWindowViewModel : ViewModelBase
{
private readonly DispatcherTimer _timer;
public MainWindowViewModel()
{
_timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)};
_timer.Start();
_timer.Tick += (o, e) => OnPropertyChanged("CurrentTime");
}
public DateTime CurrentTime { get { return DateTime.Now; } }
}
<TextBlock Text="{Binding CurrentTime, StringFormat={}{0:HH:mm tt}}" />
An example of a static wrapper for a clock and binding to it:
using System.Threading;
public static class ClockForWpf
{
private static readonly Timer timer = new Timer(Tick, null, 0, 10);
private static void Tick(object state)
{
Time = DateTime.Now;
TimeChanged?.Invoke(null, EventArgs.Empty);
}
public static event EventHandler TimeChanged;
public static DateTime Time { get; private set; }
}
<TextBlock Text="{Binding Path=(local:ClockForWpf.Time)}"/>