↑ Return to Top
class
ViewModelBase : INotifyPropertyChanged
{
internal
void
RaisePropertyChanged(
string
prop)
if
(PropertyChanged !=
null
) { PropertyChanged(
this
,
new
PropertyChangedEventArgs(prop)); }
}
public
event
PropertyChangedEventHandler PropertyChanged;
_TextProperty1;
TextProperty1
get
return
set
(_TextProperty1 != value)
_TextProperty1 = value;
"TextProperty1"
);
<
Window
. . .
xmlns:vm
=
"clr-namespace:MvvmExample.ViewModel"
DataContext
"{DynamicResource ViewModelMain}"
>
Window.Resources
vm:ViewModelMain
x:Key
"ViewModelMain"
/>
</
private
Button_Click(
object
sender, RoutedEventArgs e)
var win =
Window1 { DataContext =
ViewModelWindow1(tb1.Text) };
win.Show();
.Close();
ViewModelWindow1(
lastText)
_TestText = lastText;
Button
Content
"Change Text"
Command
"{Binding ChangeTextCommand}"
CommandParameter
"{Binding SelectedItem, ElementName=dg1}"
ChangeText(
selectedItem)
(selectedItem ==
)
TestText =
"Please select a person"
;
else
var person = selectedItem
as
Person;
TestText = person.FirstName +
" "
+ person.LastName;
static
DialogCloser
readonly
DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult"
typeof
(
bool
?),
(DialogCloser),
PropertyMetadata(DialogResultChanged));
DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
var window = d
Window;
(window !=
) window.Close();
SetDialogResult(Window target,
? value)
target.SetValue(DialogResultProperty, value);
x:Class
"MvvmExample.ViewModel.Window1"
WindowStartupLocation
"CenterScreen"
xmlns
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
"http://schemas.microsoft.com/winfx/2006/xaml"
Title
"Window1"
Height
"300"
Width
"400"
xmlns:helpers
"clr-namespace:MvvmExample.Helpers"
helpers:DialogCloser.DialogResult
"{Binding CloseWindowFlag}"
? _CloseWindowFlag;
? CloseWindowFlag
_CloseWindowFlag; }
_CloseWindowFlag = value;
"CloseWindowFlag"
virtual
CloseWindow(
? result =
true
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
Action(() =>
CloseWindowFlag = CloseWindowFlag ==
?
: !CloseWindowFlag;
}));
NextExample(
parameter)
Window2();
CloseWindow();
ViewModelWindow2 : DependencyObject
Person SelectedPerson
(Person)GetValue(SelectedPersonProperty); }
{ SetValue(SelectedPersonProperty, value); }
DependencyProperty SelectedPersonProperty =
DependencyProperty.Register(
"SelectedPerson"
(Person),
(ViewModelWindow2),
UIPropertyMetadata(
));
The main drawback to Dependency Properties for general MVVM use is they need to be handled on the UI layer.
For more on the INPC vs DP debate, read the following:
This example also shows how a command can also control whether a button is enabled or not, through it's CanExecute delegate. This again moves away from controller and more into behavior. The action cannot even be triggered if conditions are not met, and this is clear to the user as the button is disabled.
We are not using the command parameter in this example, but relying on a ViewModel property to be populated with the selected item. If there is none, the CanExecute method returns false, which disables the button. All encapsulated in the command, nice clean code.
public ViewModelWindow2()
People = FakeDatabaseLayer.GetPeopleFromDatabase();
NextExampleCommand = new RelayCommand(NextExample, NextExample_CanExecute);
bool NextExample_CanExecute(object parameter)
return SelectedPerson != null;
To close the window in this example, we still use the Attached Property in the Window XAML, but the property is a Dependency Property in the ViewModel.
?)GetValue(CloseWindowFlagProperty); }
{ SetValue(CloseWindowFlagProperty, value); }
// Using a DependencyProperty as the backing store for CloseWindowFlag. This enables animation, styling, binding, etc...
DependencyProperty CloseWindowFlagProperty =
Used simply as follows:
Window3(SelectedPerson);
CloseWindowFlag =
A POCO class in WPF/MVVM terms is one that does not provide any PropertyChanged events.
PocoPerson
FirstName {
; }
LastName {
int
Age {
This would usually be legacy code modules, or converting from WinForms. This next example demonstrates how things start to break, when you don't use PropertyChanged events. At first, everything seems fine. Selected item is updated in all, you can change properties of existing people, and add new people through the DataGrid. Those actions are all UI based actions, which change Dependency Properties like TextBox.Text, and automatically fire the PropertyChanged event. However, the TextBox should actually be showing a time stamp, as set by the code behind Dispatcher Timer.
ViewModelWindow3(Person person)
. . . snip . . .
timer =
DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick +=
EventHandler(timer_Tick);
timer.Start();
timer_Tick(
sender, EventArgs e)
TextProperty1 = DateTime.Now.ToString();
Furthermore, clicking the Button to add a new person does not seem to work, but it did. If you then try to add a user in the DataGrid, the binding updates, and shows the previously added user. All a bit broken.
Just as an alternative, this ViewModel has a custom event, to signify closure, which is handled in code behind when the ViewModel is attached.
Window3(Person person)
InitializeComponent();
var vm =
ViewModelWindow3(person);
DataContext = vm;
vm.CloseWindowEvent +=
System.EventHandler(vm_CloseWindowEvent);
vm_CloseWindowEvent(
sender, System.EventArgs e)
The following window is almost identical to the previous POCO example. However TextProperty1 now triggers a PropertyChanged event in it's setter.
//The fix
Now you will find the time stamp property shows through onto the TextBox as shown below. However, with events coming from the INPC interface, the other UI bindings are now even more broken. (Try the sample project)
The behaviour of closing a window, could happily be kept with the window, if you believe that is where it belongs. One way of always assuring any ViewModel has the expected event shown above would be to make our ViewModels (ideally in the ViewModelBase) inherit an interface that defines the event. We can then safely cast any ViewModel back to this interface, and attach a handler to the expected event.
Window4()
DataContextChanged +=
DependencyPropertyChangedEventHandler(Window4_DataContextChanged);
Window4_DataContextChanged(
sender, DependencyPropertyChangedEventArgs e)
var dc = DataContext
IClosableViewModel;
dc.CloseWindowEvent +=
EventHandler(dc_CloseWindowEvent);
The most important message to learn from all of this is that if you can convert the base (model) classes that you use to INPC properties, DO SO NOW. You will save yourself a world of pain, unnecessary data marshalling through wrappers, and a whole load of code that can introduce bugs and dependencies to your implementation. But what if you have a Business Object that handles all the work, like an existing database module, or web service client?
ObservableCollection<PocoPerson> _People;
ObservableCollection<PocoPerson> People
_People =
ObservableCollection<PocoPerson>(personnel.GetEmployees());
_People;
ReportTitle
personel.ReportTitle;
(personel.ReportTitle != value)
personnel.ReportTitle = value;
"ReportTitle"
MvvmExample.Model.PersonelBusinessObject.StatusType _BoStatus;
MvvmExample.Model.PersonelBusinessObject.StatusType BoStatus
_BoStatus;
(_BoStatus != value)
_BoStatus = value;
"BoStatus"
CheckStatus(
(_BoStatus != personel.Status)
BoStatus = personel.Status;
This example shows a complete and virtually codeless master/detail, CRUD control (for databases, web services, etc) By master/detail, we mean there is a master list of objects. You then select a list item to get the item details in a separate box. By CRUD, we mean Create, Update and Delete functionality. The three things you do to objects in a collection, or a database. Normally, in a big application, we would design a stand-alone user control for an edit form, but here is a quick trick to produce the whole, fully wired edit form, using the ItemsTemplate for an ItemsControl. The actual edit grid is a DataTemplate, instead of an actual control. This DataTemplate is used as the ItemTemplate for the ItemsControl:
ItemsControl
BindingGroup
"{Binding UpdateBindingGroup, Mode=OneWay}"
ItemTemplate
"{StaticResource UserGrid}"
ItemsSource
"{Binding SelectedPerson, Converter={StaticResource SelectedItemToItemsSource}}"
SelectedItemToItemsSource : IValueConverter
Convert(
value, Type targetType,
parameter, System.Globalization.CultureInfo culture)
(value ==
List<
>() { value };
ConvertBack(
throw
NotImplementedException();
Binding to properties of the selected person, is only the immediate DataContext of the generated item - direct and easy.
TextBox
Text
"{Binding FirstName, BindingGroupName=Group1, UpdateSourceTrigger=Explicit}"
Grid.Column
"1"
Foreground
"Red"
"Cancel"
"{Binding DataContext.CancelCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Margin
"4,0"
"{Binding . . . BindingGroupName=Group1, . . ." />
. . . />
BindingGroup _UpdateBindingGroup;
BindingGroup UpdateBindingGroup
_UpdateBindingGroup;
(_UpdateBindingGroup != value)
_UpdateBindingGroup = value;
"UpdateBindingGroup"
ViewModelWindow5()
UpdateBindingGroup =
BindingGroup { Name =
"Group1"
};
DoCancel(
param)
UpdateBindingGroup.CancelEdit();
DoSave(
UpdateBindingGroup.CommitEdit();
var person = SelectedPerson
PocoPerson;
(SelectedIndex == -1)
personel.AddPerson(person);
"People"
// Update the list from the data source
personel.UpdatePerson(person);
SelectedPerson =