I started learning WPF few days ago and have been creating some test projects to evaluate my understanding of the materials as I learn them. According to this article, "a property can only be bound if it is a dependency property." To test this, I used two different patterns:
1. Using MVVM pattern (sort of)
I created the Movie
model:
public class MovieModel
{
private string _movieTitle;
private string _rating;
public string MovieTitle
{
get { return _movieTitle; }
set { _movieTitle = value; }
}
public string Rating
{
get { return _rating; }
set { _rating = value; }
}
}
and the MovieViewModel
view model:
public class MovieViewModel
{
MovieModel _movie;
public MovieViewModel()
{
_movie = new MovieModel { MovieTitle = "Inception (Default)" };
}
public MovieModel Movie
{
get { return _movie; }
set { _movie = value; }
}
public string MovieTitle
{
get { return Movie.MovieTitle; }
set { Movie.MovieTitle = value; }
}
}
And finally in the my main View
, I set the DataContext to an instance of the MovieViewModel
class:
<Window x:Class="MVVMTestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVMTestProject.ViewModels"
Name="MainWindowElement"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MovieViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Width="150" Text="{Binding Path=MovieTitle}"></TextBox>
</StackPanel>
</Grid>
This works fine, when I run the application I see Inception (Default)
in the textbox, even though none of the properties in the Model
or ViewModel
are DependencyProperties
. Now the second try was by:
2. Using a property in the code behind of MainView
In the code behind of my main view, I put:
private string _myText;
public MainWindow()
{
InitializeComponent();
MyText = "Some Text";
}
public string MyText
{
get { return _myText; }
set { set _myText = value; }
}
And then I changed my view to:
<Window x:Class="MVVMTestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVMTestProject.ViewModels"
Name="MainWindowElement"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MovieViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Width="150" Text="{Binding ElementName=MainWindowElement, Path=MyText}"></TextBox>
</StackPanel>
</Grid>
But that didn't work. When I run the application, the textbox is blank. So I changed the property to a dependency property, and oddly enough, it worked. So I don't understand why the properties on the Model and ViewModel can be regular properties, but the code-behind properties have to be dependency properties in order to bind to? Is it because the ViewModel is set to the DataContext of the view and the parser is smart enough to realize this or is there another reason?
Also a not-very-relevant question: I noticed in almost every singe article on WPF people use a local variable and then define a getter and setter for it, even though we all know in .NET 2.5 (I believe?) and above, we can just use the get; set;
syntax without having to define a local member variable. So is there a specific reason for this?
Thanks in advance.
DependencyProperty
is only needed on the FrameworkElement
(DependencyObject) that will be used as the target of binding, meaning the property on which you will apply {Binding} extension through XAML or set the Binding through code. In your example only TextBox.Text
is required to be DependencyProperty
. In your second example Binding didn't work because you don't have mechanism in place that will send notification from binding source that the property value has changed. Usually that is done by implementing INotifyPropertyChanged
interface and raising PropertyChanged event inside property setter. When you changed the property to a DependencyProperty
you also got the notification mechanism so the property change is propagated to the target (TextBox). So you could make it work by raising PropertyChanged
event without the need for DependencyProperty
.
Note that there are other uses that require DependencyProperty
other than bindings (Styles, animations...)
PropertyChanged
event is probably the main reason for implementing properties with backing field (and not as Auto properties). As you need PropertyChanged
event in the property setter.
Picking up a second question tucked in at the end of your post:
Also a not-very-relevant question: I noticed in almost every singe article on WPF people use a local variable and then define a getter and setter for it, even though we all know in .NET 2.5 (I believe?) and above, we can just use the
get; set;
syntax without having to define a local member variable. So is there a specific reason for this?
In your code you gave an example like this:
private string _movieTitle;
public string MovieTitle
{
get { return _movieTitle; }
set { _movieTitle = value; }
}
That code is exactly what the compiler generates when you write this instead:
public string MovieTitle { get; set; }
The reason you see something like the first form a lot in WPF, is because we want WPF bindings to react when property values change, and in order to achieve this we need to add change notification via INotifyPropertyChanged
.
There are many ways of doing this, but in modern C# you can write something like this:
public class MovieViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _movieTitle;
public string MovieTitle
{
get { return _movieTitle; }
set
{
if (_movieTitle == value)
return;
_movieTitle = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the setter, we raise the PropertyChanged
event with the string name of the property, MovieTitle
, which the compiler passes in for us because of the [CallerMemberName]
attribute, and default value of null
so we don't have to pass anything when we call it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With