PropertyChanged for a custom class is always null - c#

I have a problem with INotifyPropertyChanged and binding, I watched dozen questions here, but problem still exists.
My XAML code:
<Window x:Class="DAA.IST.ChoosePC.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:DAA.IST.ChoosePC"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:Question x:Key="ExpertSystem" Text="question"/>
</Window.Resources>
<Grid DataContext="{StaticResource ExpertSystem}">
<Label x:Name="Description" Content="Определение опимальной конфигурации ПК" HorizontalAlignment="Left" Margin="100,30,0,0" VerticalAlignment="Top"/>
<Button x:Name="button" Content="" HorizontalAlignment="Left" Margin="204,167,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
<Button x:Name="button1" Content="" HorizontalAlignment="Left" Margin="284,167,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
<Label x:Name="QuestionLable" Content="{Binding Text}" HorizontalAlignment="Left" Margin="100,70,0,0" VerticalAlignment="Top"/>
<Label x:Name="Result" Content="" HorizontalAlignment="Left" Margin="100,210,0,0" VerticalAlignment="Top"/>
</Grid>
I list here full code, because I really don't know there problem is, sorry. Main window code:
public partial class MainWindow : Window
{
public static IQuestion Question;
public MainWindow()
{
InitializeComponent();
Question = new Question
{
Text = "question",
Answers = new List<IAnswer>
{
new Answer {Text = "answer", NextQuestionNumber = 0},
new Answer {Text = "answer1", NextQuestionNumber = 0}
}
};
DataContext = Question;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Question.Answer(0);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Question.Answer(1);
}
}
And Questioncode, this class implements INotifyPropertyChanged:
public class Question : IQuestion, INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public IList<IAnswer> Answers { get; set; }
public void Answer(int number)
{
MainWindow.Question.Answers[number].Number = number;
MainWindow.Question.Answers[number].SetNextQuestion();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Please help me, sure I miss or don't know something. Also if someone can give me advise about changing of IList<IAnswer> Answers, I will be thankful to him^^

First of all, it is bad practice to refer MainWindow inside your question class.
You should use commands in this context i.e. you should define a command in your DataContext( question here) and bind that Command to your two Button's Command property.
For example:
Class Question : INotifyPropertyChanged, IQuestion
{
public ICommand ButtonClickCommand { get; set;}
public Question()
{
//Bind ButtonClickCommand to Event Handler/ A Method
ButtonClickCommand = new DelegateCommand(EventHandlerName);
}
public void EventHandlerName()
{
}
}
Also, take a private _answers field in the class and raise OnPropertyChanged event from Setter of Answers property too, to utilize this list in Notification mechanism i.e.
public IList<IAnswer> Answers {
get { return _answers }
set { _nswers = value;
OnPropertyChanged("Answers");
}
And, you have mentioned that you want to chnge the content of label and buttons. But to change the content of label and Buttons, you need to bind the 'Content' of these controls to Properties in Question class as well. I don't see any binding in your XAML as of now.

Finally, I found my mistake. I should change property, not create new instances. If I create new one, old value remains the same. It is connected with reference types realization some way.
MainWindow.Question.Text = "question";
MainWindow.Question.Answers[0].Text = "answer0";
MainWindow.Question.Answers[0].NextQuestionNumber = number0;
MainWindow.Question.Answers[1].Text = "answer1";
MainWindow.Question.Answers[1].NextQuestionNumber = number1;

Related

WPF Binding and Observable Model fail

In a simple trying-to-learn-WPF experiment I'm trying to bind a property ("InternalName") of an instance of MyModel to the contents of TextBlock "MainWindowTextBlock". Clicking the ``ChangeNameButton" changes the InternalName property of mymodel, but that property change never makes it through to the TextBlock. Nothing happens. What am I doing wrong?
XMAL
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:UserControlExperiments"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Grid.Row ="0">
<Button Width="100" Height="20" Name="ChangeName" Content="Change the Name" Click="ChangeNameButtonClick"/>
<TextBlock Text=""/>
<TextBlock Name="MainWindowTextBox" Width="100" Height="20" Text="{Binding Path = mymodel.InternalName, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Window>
CODE BEHIND
public partial class MainWindow : Window
{
public MyModel mymodel;
public MainWindow()
{
InitializeComponent();
DataContext = this.DataContext;
mymodel = new MyModel("The old name");
}
private void ChangeNameButtonClick(object sender, RoutedEventArgs e)
{
mymodel.InternalName = "A new name!";
}
}
public class MyModel : INotifyPropertyChanged
{
private string internalname;
public event PropertyChangedEventHandler PropertyChanged;
public MyModel(string nm)
{
InternalName = nm;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string InternalName
{
get { return internalname; }
set
{
if (internalname != value)
{
internalname = value;
OnPropertyChanged("InternalName");
}
}
}
}
}
The following markup tries to bind to a property named "mymodel" of the current DataContext of the TextBlock, which is inherited from the parent window:
<TextBlock Name="MainWindowTextBox"
Text="{Binding Path = mymodel.InternalName}"/>
So you need to set the DataContext of the window to itself:
DataContext = this;
And you also need to make mymodel a public property since you cannot bind to fields:
public MyModel mymodel { get; }
Then it should work but you probably also want to change the name of the property to comply with the C# naming standards.
You can also remove Mode=TwoWay from the binding. It makes no sense for a TextBlock.

Binding to a UserControl in WPF using C#

Preface
The control I am giving as an example is an sample work for a larger project. I have already had some help from the community on Stackoverflow ironing out some of the finer points of bindings within the control. The surprise has been that I am having an issue binding in the control's hosting form.
I have read and researched around DependencyProperty for a lot of hours. I was not a WPF developer at the start of the year but I am now covering the role because of a death in the business, and I accept this is a big hill to climb.
The question is what is missing here in my:
The hosting form's XAML code
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AControl="clr-namespace:AControl;assembly=AControl" x:Class="DependencySampler.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>
</Grid>
The code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new viewModelBinding();
BeSelected = new modelMain("Yellow", "#FFFFE0");
}
public modelMain BeSelected
{
get { return ((viewModelBinding)DataContext).Selected; }
set { ((viewModelBinding)DataContext).Selected = value; }
}
}
The ViewModel
public class viewModelBinding :ViewModelBase
{
modelMain sel = new modelMain("Red", "#FF0000");
public modelMain Selected
{
get { return sel; }
set { SetProperty(ref this.sel, value, "Selected"); }
}
}
The next section is the control itself.
The Model
public class modelMain:ViewModelBase
{
public modelMain(string colName, string hexval)
{
ColorName = colName;
HexValue = hexval;
}
string colorName;
public string ColorName
{
get { return colorName; }
set { SetProperty(ref this.colorName, value, "ColorName"); }
}
string hexValue;
public string HexValue
{
get { return hexValue; }
set { SetProperty(ref this.hexValue, value, "HexValue"); }
}
}
The ViewModel
public class viewModelMain:ViewModelBase
{
ObservableCollection<modelMain> val = new ObservableCollection<modelMain>();
public ObservableCollection<modelMain> ColorsList
{
get { return val; }
set { SetProperty(ref this.val, value, "Colors"); }
}
modelMain selectedColor;
public modelMain SelectedColour
{
get{return selectedColor;}
set { SetProperty(ref this.selectedColor, value, "SelectedColour"); }
}
public void SetCurrentColor(modelMain col)
{
SelectedColour = this.val.Where(x => x.ColorName == col.ColorName).FirstOrDefault();
}
public viewModelMain()
{
val.Add(new modelMain("Red", "#FF0000"));
val.Add(new modelMain("Blue", "#0000FF"));
val.Add(new modelMain("Green", "#008000"));
val.Add(new modelMain("Yellow", "#FFFFE0"));
SelectedColour = new modelMain("Blue", "#0000FF");
}
}
The UserControl XAML
<UserControl x:Class="AControl.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="32" d:DesignWidth="190">
<Grid>
<ComboBox x:Name="cboValue"
SelectionChanged="cboValue_SelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding ColorList, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedValue="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="10"
Height="10"
Margin="5"
Background="{Binding ColorName}"/>
<TextBlock Width="35"
Height="15"
Margin="5"
Text="{Binding ColorName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
The UserControl Code behind
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
ObservableCollection<modelMain> colorList = new viewModelMain().ColorsList;
public ObservableCollection<modelMain> ColorList
{
get { return colorList; }
set { colorList = value; }
}
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(
"SelectedColor",
typeof(modelMain),
typeof(UserControl1),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedColorChanged),
new CoerceValueCallback(CoerceSelectedColorCallback)));
private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = (UserControl1)d;
uc.SelectedColor = (modelMain)e.NewValue;
}
private static object CoerceSelectedColorCallback(DependencyObject d, object value)
{
return (modelMain)value;
}
public modelMain SelectedColor
{
get { return (modelMain)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
private void cboValue_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dat = sender as ComboBox;
SelectedColor = (modelMain)dat.SelectedValue;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//var dat = sender as ComboBox;
////SelectedColor = (modelMain)dat.SelectedValue;
//SelectedColor = (modelMain)this.SelectedColor;
}
}
Please note that in the code behind there is unused code but within the sample I have used then for placing break points
I understand that no DataContext should exist in the UserControl because it precludes one in the hosting form.
The Question
I was expecting the this line would be sufficient in the hosting form.
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>
But it does not seem to do what I expected. I can see the BeSelected be initialised and it is holding a value but when the form loads I am expecting the colour yellow to enter the UserControl's and set DependencyProperty SelectedColor. This is not happening why and how can I get it to happen?
To get you example working, do the following (for the most part, implement what the commenters said):
The hosting form's XAML code
<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, RelativeSource={RelativeSource AncestorType=Window}}}"/>
The Mode doesn't really matter since MainWindow doesn't implement INPC nor does it ever know when ((viewModelBinding)DataContext).Selected (and therefor, BeSelected) is changed. Actually, like Joe stated, OneWayToSource doesn't work... RelativeSource was needed because BeSelected is a property of the MainWindow - not MainWindow's DataContext.
modelMain
modelMain needs to implement IEquatable (like Janne commented). Why? Because BeSelected = new modelMain(...) creates a new modelMain which is not one of the items in the ComboBox's ItemsSource (ColorList). The new object may have the same property values as one of the items but that doesn't make them equal (different objects = different address in memory). IEquatable gives you the opportunity to override that.
public class modelMain : ViewModelBase, IEquatable<modelMain>
{
...
public bool Equals(modelMain other)
{
return (HexValue == other.HexValue);
}
}
viewModelMain's ColorList's setter is calling SetProperty with property name "Colors" when it should be "ColorsList". It's not being used so it doesn't stop your example from working but it's still wrong.

Data binding in WPF using code behind

I have a scenario where I would want to bind a string property to a text box on the UI when the string property changes.I would want to change the property in the code behind.Please find my work below :
XAML :
<Window x:Class="databinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:databinding"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding mobile1}" Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding mobile2}" Height="23" HorizontalAlignment="Left" Margin="10,43,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="10,76,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
Please find the MainWindow.xaml.cs below :
namespace databinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
void button1_Click(object sender, RoutedEventArgs e)
{
talk ta = new talk();
ta.test();
}
}
}
Please find the class where I have defined my properties :
mobile.cs :
namespace databinding
{
public class mobile:INotifyPropertyChanged
{
string mobile1model;
string mobile2model;
public string mobile1 { get { return mobile1model; } set { mobile1model = value; OnPropertyChanged(new PropertyChangedEventArgs("mobile1")); } }
public string mobile2 { get { return mobile2model; } set { mobile2model = value; OnPropertyChanged(new PropertyChangedEventArgs("mobile2")); } }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
}
now I have a test class where I change the property value :
test.cs :
namespace databinding
{
public class talk:MainWindow
{
public void test()
{
mobile mb = new mobile();
mb.mobile1 = "This is mobile 1";
mb.mobile2 = "This is mobile 2";
}
}
}
The problem statement here is that when I click the button1 the properties get updated but the textboxes on the UI does not get updated,also I am looking if the textboxes are updates as soon as the property changes or is it going to wait till the second property changes to get updated? Appreciate your support for this query.
Thanks in advance.
Create one instance of mobile class, set it as Window.DataContext and call test() on that instance
public partial class MainWindow : Window
{
private readonly mobile _mb = new mobile();
public MainWindow()
{
InitializeComponent();
DataContext = _mb;
}
public void test()
{
_mb.mobile1 = "This is mobile 1";
_mb.mobile2 = "This is mobile 2";
}
void button1_Click(object sender, RoutedEventArgs e)
{
this.test();
}
}

Trying to understand how i can bind a WPF DP in a different class

I am trying WPF to develop a tiny scoreboard.
In this project i have 3 XAML files.
ControlDisplay.xaml : Here is where i set the points for team 1 and team 2 in the scoreboard. Right now i only have 1 textbox for the scoreboard title.
Layout1.xaml : First layout, contains only a title for now.
Layout2.xaml : Second layout, same as above, only contains a title.
My idea is as following. I update one singleton class that has one property Title. Both Layout1 and Layout2's label for the title will bind to this singleton class property Title.
I created the basic structure for it.
ControlDisplay.xaml:
public partial class ControlDisplay : Window
{
private IScoreboardData _scoreboardData;
private Layout1 _layout1;
private Layout2 _layout2;
public ControlDisplay()
{
InitializeComponent();
_scoreboardData = SimpleInjectorContainer.Container.GetInstance<IScoreboardData>();
}
private void ShowLayout1(object sender, RoutedEventArgs e)
{
_scoreboardData.Title = "Test";
_layout1 = new Layout1();
_layout1.Show();
}
private void ShowLayout2(object sender, RoutedEventArgs e)
{
_scoreboardData.Title = "Test";
_layout2 = new Layout2();
_layout2.Show();
}
}
Layout1.xaml.cs (layout2 is a copy of layout1 codewise, just a different class name)
public partial class Layout1 : Window
{
private IScoreboardData _scoreboardData;
public Layout1()
{
_scoreboardData = SimpleInjectorContainer.Container.GetInstance<IScoreboardData>();
InitializeComponent();
}
}
Layout1.xaml
<Window x:Class="SmallScoreboard.Layout1" .... x:Name="LayoutOne">
<StackPanel>
<Label DataContext="{Binding ElementName=LayoutOne}" Content="{Binding _scoreboardData.Title}" />
</StackPanel>
</Window>
ScoreboardData.cs
public ScoreboardData : IScoreboardData
{
public string Title { get; set; }
}
This obviously does not work since i don't register a dependency property anywhere? How can i register a dependency property inside the ScoreboardData class? or is there a better way to solve this?
I want to be able to add more layouts in the future and i hope that i don't have to add the base binding logic to each and everyone of those layout(x).xaml.cs files.
Update
This is my Layout1.xaml file right now:
<Window x:Class="Simple_Scoreboard.Layout1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Layout" Height="500" Width="800"
ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
WindowStyle="None"
AllowsTransparency="True"
ResizeMode="CanResizeWithGrip"
x:Name="LayoutOne" MouseLeftButtonDown="DWindow_MouseLeftButtonDown">
<StackPanel>
<Label Content="{Binding Path=Title, Mode=OneTime}" FontSize="30" HorizontalAlignment="Center" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center" Margin="0,10,0,0" FontWeight="Bold"></Label>
<Button Content="Button" Click="Button_Click_1"/>
</StackPanel>
</Window>
and the Layout1.xaml.cs
public partial class Layout1 : Window
{
public IScoreboardData _scoreboardData;
public Layout1()
{
InitializeComponent();
_scoreboardData = ScoreboardContainer.Container.GetInstance<IScoreboardData>();
DataContext = _scoreboardData;
}
private void DWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_scoreboardData.Title = "Click change title";
}
}
and finally the ScoreboardData class:
class ScoreboardData : IScoreboardData, INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I think your problem is in binding to a private field _scoreboardData;
you should make it a public property. But much better solution would be to bind to a window DataContext.
in the window constructor
public Layout1()
{
_scoreboardData = SimpleInjectorContainer.Container.GetInstance<IScoreboardData>();
InitializeComponent();
DataContext = _scoreboardData;
}
In the XAML
<Window x:Class="SmallScoreboard.Layout1" .... x:Name="LayoutOne">
<StackPanel>
<Label Text="{Binding Title}" />
</StackPanel>
</Window>
This way you have your scoreBoardData as Window DataContext and all bindings without explicitly specified source will bind to that object.
UPDATE:
ScoreboardData should implement INotifyPropertyChanged..
public class ScoreboardData :IScoreboardData, System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs("Title"));
}
}
}

Simple databinding not working

I'm new to WPF and I'm trying to make a simple app, a stopwatch. It works fine if I'm not doing the data binding. Here's my XAML.
<Window x:Class="StopWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StopWatch"
Title="MainWindow" Height="318" Width="233">
<Window.Resources>
<s:StopWatchViewModel x:Key="swViewModel" x:Name="swViewModel"></s:StopWatchViewModel>
</Window.Resources>
<Grid DataContext="{StaticResource swViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="128*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Height="49" HorizontalAlignment="Left" Margin="42,50,0,0" Name="txtTime" Text="{Binding Path=Message}" VerticalAlignment="Top" Width="147" FontSize="20" TextAlignment="Center" />
<Button Content="Start" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,15,0,0" Name="startBtn" VerticalAlignment="Top" Width="58" Click="startBtn_Click" />
<Button Content="Stop" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="76,15,0,0" Name="stopBtn" VerticalAlignment="Top" Width="58" Click="stopBtn_Click" />
<Button Content="Reset" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="140,15,0,0" Name="resetBtn" VerticalAlignment="Top" Width="59"/>
</Grid>
and here is the code in MainWindow
public partial class MainWindow : Window
{
private StopWatchViewModel stopwatch;
public MainWindow()
{
InitializeComponent();
stopwatch = new StopWatchViewModel();
}
private void startBtn_Click(object sender, RoutedEventArgs e)
{
stopwatch.Start();
}
private void stopBtn_Click(object sender, RoutedEventArgs e)
{
stopwatch.Stop();
}
}
and here's the code in StopWatchViewModel.cs
class StopWatchViewModel : INotifyPropertyChanged
{
private DispatcherTimer timer;
private Stopwatch stopwatch;
private string message;
public event PropertyChangedEventHandler PropertyChanged;
public string Message
{
get
{
return message;
}
set
{
if (message != value)
{
message = value;
OnPropertyChanged("Message");
}
}
}
public StopWatchViewModel()
{
timer = new DispatcherTimer();
stopwatch = new Stopwatch();
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
stopwatch.Reset();
}
public void Start()
{
stopwatch.Start();
}
public void Stop()
{
stopwatch.Stop();
}
private void timer_Tick(object sender, EventArgs e)
{
Message = stopwatch.Elapsed.ToString(); // Doesn't work.
// Message = "hello"; does not work too!
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I don't know where I got it wrong.
EDIT: I got it working. So here's the working code for anyone's reference.
XAML, change the original to this
<Window x:Class="StopWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StopWatch"
Title="MainWindow" Height="318" Width="233">
<Grid> // partial code
and in behind code, change the constructor based on Erno's suggestion.
public MainWindow()
{
InitializeComponent();
viewModel = new StopWatchViewModel();
this.DataContext = viewModel;
}
Thanks guys!
Your problem here is that you don't have any mechanism for letting WPF know that your property is updated. Basically you have two options here:
Make Message into a Dependancy Property.
Implement INotifyPropertyChanged so that it let's the GUI know when the message have been updated.
To make sure that you have all parts for getting INotifyPropertyChanged to work, check that you did all of this:
Define the event PropertyChanged.
Make a private NotifyPropertyChanged method to raise the event. This method should take a string parameter (name of the property) and raise the event like this : PropertyChanged(this, new PropertyChangedEventArgs(<nameofproperty>). The reason to make this method is to put the null check and invocation details in one place.
In the property setter, call NotifyPropertyChanged with the correct name (case-sensitive) of the property after the value has changed.
Just replace your Message property like this and it will work:
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string),
typeof(MainWindow), new UIPropertyMetadata(String.Empty));
EDIT after posting my solution I noticed you changed all the code into a ViewModel solution... feel free to ignore or go back to your first set of code.
In your new code you are creating TWO instances of the ViewModel, one in code and one in the resources. That is not good as you are manipulating the one in code and binding to the one in the resources(xaml)
EDIT:
change your constructor to this:
public MainWindow()
{
InitializeComponent();
stopwatch = new StopWatchViewModel();
this.DataContext = stopwatch;
}
That's all
Changes to the property won't get noticed if you don't implement INotifyPropertyChanged correctly. So you should do that first. Maybe you missed something when you did it earlier.

Categories