I've created a Textbox with placeholder text and a clear button. I've implemented it using a view model for the data context, and using a style with target type TextBox. In xaml, using it is pretty simple.
<TextBox DataContext="{Binding NameBox}" Style="{StaticResource placeholder}"/>
The way I've implemented the view model, though, smells funny to me:
public class PlaceholderTextBoxViewModel : NotifiableViewModelBase {
private string text;
public string Text {
get => text;
set {
text = value;
OnTextChange(text);
}
}
public string PlaceholderText { get; set; }
public RelayCommand ClearCommand => new RelayCommand(() => Text = "");
private event Action<string> OnTextChange;
public PlaceholderTextBoxViewModel(ref string text, string placeholderText, Action<string> changeHandler = null) {
OnTextChange = changeHandler ?? (_ => { });
Text = text;
PlaceholderText = placeholderText;
}
}
In case it doesn't smell too bad to you yet, check out how it's used
private string _name;
public string Name {
get => _name;
set {
_name = value;
System.Console.WriteLine(_name); // needed to silence auto prop error
}
}
public PlaceholderTextBoxViewModel NameBox { get; }
// in the constructor...
NameBox = new PlaceholderTextBoxViewModel(ref _name, "Exam Name", t => Name = t);
It definitely doesn't seem right that I need to pass an explicit setter (the changeHandler) to the PlaceholderTextBoxViewModel. It seems, indeed, that the ref I'm passing is never really used (and is only necessary at all -- though not as a ref -- if there is to be pre-existing text in the box).
I've never used refs before and I must be doing something wrong. I have also tried pointing everything that uses the Name property (in the final code excerpt) to the _name field but that doesn't work, the field isn't properly updated, or at least isn't "communicating" its updates (in various uses, CanExecutes are not updated, SearchPredicates are not refreshed, etc). I'm using MVVMLight, and I imagine that changing a field's value doesn't trigger OnPropertyChanged -- if the field's value is even changing at all.
How do I get the ref to work correctly? Am I doing this completely wrong?
I understand that there are other ways to implement this TextBox with its clear command, even in pure MVVM (namely, if I put the ClearCommand in the consuming VM instead of the textbox's VM itself, then the textbox doesn't need to have a VM at all). But I'd really like to know how to make sense of my attempted solution, if only for a better understanding of C# and of refs.
Problem here seems to be an architectural one. MVVM is a tiered architecture that looks like this:
Model -> View Model -> View
Model is the lowest tier, view is the highest. More importantly, each tier does not have any direct visibility into any of the tiers above it.
The problem in your example is that you've broken separation of concerns. You have a parent class creating the PlaceholderTextBoxViewModel, which implies it's in the view model. However, that same class contains a "Name" property, which is actually data that should be in your model layer. Your existing architecture is such that your view model cannot see the data layer, so you've effectively had to set up your own property change notification mechanism, using the ref and delegate, to keep your model and view model synchronized.
Throw it all out, and start again. Your model should contain POCOs, so start with something like this:
public class MyModel
{
public string Name {get; set;}
}
Then, when you create your view model, pass an instance of this class into its constructor so that it has visibility:
public class MyViewModel : NotifiableViewModelBase
{
private MyModel Model;
public MyViewModel(MyModel model) => this.Model = model;
public string Text
{
get => this.Model.Name;
set
{
this.Model.Name = value;
RaisePropertyChanged(() => this.Text);
}
}
// ... etc ....
}
I've stuck to your nomenclature of using "Name" in the model and "Text" in the view model, in practice they're usually the same, but that's up to you. Either way, you still have property change notification in your view model, and it's the view model layer updating the model layer.
Obviously there are lots of variations on this. If you don't want changes to propagate through to your model layer immediately (and there are plenty of cases where you may not want that) then give Text a backing field (_Text) and only do the synchronization at the points your want it to occur. And of course, if you want to go one step further then your model classes could instead implement interfaces, and you can use dependency injection to inject those interfaces into the view model classes instead of giving them access to the actual implementations themselves.
Above all else, keep in mind that the sole purpose of the view model is to prepare the model layer data for consumption by the view. Anything else...data, domain, business logic etc...all of that belongs in your model layer, which shouldn't have any visibility into the view model layer at all.
The ref would only make sense, if you are going to change the value.
In this case you can use the OnTextChange event.
public PlaceholderTextBoxViewModel(ref string text, string placeholderText, Action<string> changeHandler = null)
{
OnTextChange = newValue =>
{
text = newValue; // <- value back to the ref
changeHandler?.Invoke(newValue);
}
Text = text;
PlaceholderText = placeholderText;
}
BTW your solution is somehow much too complicated. Keep the ViewModel as simple and abstract as possible. In this case a simple Name property is enough.
Leave it up the the UI developers which control they use and how they will implement a clear the control logic.
Here an example
ViewModel
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace WpfApp1
{
public class MainViewModel : ReactiveObject
{
[Reactive] public string Name { get; set; }
}
}
View
<Window
x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<StackPanel
Width="200"
VerticalAlignment="Center">
<Label Content="{Binding Name}" />
<DockPanel>
<Button
DockPanel.Dock="Right"
Content="x"
Width="20"
Click="NameTextBoxClearButton_Click"/>
<TextBox x:Name="NameTextBox"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</DockPanel>
</StackPanel>
</Grid>
</Window>
View CodeBehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void NameTextBoxClearButton_Click( object sender, RoutedEventArgs e )
{
NameTextBox.Text = string.Empty;
}
}
The simplest solution would be to correct your binding. You don't have to manually forward the data by trying to implement your own change notification system.
Just make sure that your data source always implements INotifyPropertyChanged and properly raises the INotifyPropertyChanged.PropertyChanged event from each property set method. Then bind directly to this properties:
class PlaceholderTextBoxViewModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get => this.text;
set
{
this.text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The binding syntax allows to reference nested properties.
In your special case, the {Binding} expression must be:
<TextBox DataContext="{Binding NameBox.Text}" />
Now the TextBox.Text value is automatically propagated to the PlaceholderTextBoxViewModel.Text property and the constructor becomes parameterless.
Related
Databinding is a difficult concept that I still can't quite grasp despite reading through dozens of topics already.
I want to have a TextBox that would change its text every time a field 'status' is being changed in my code-behind, for debugging purposes.
Here's what I have so far:
public partial class ReviewsControl : UserControl
{
private Status status = MainControl.AppStatus;
public string StatusDisplay
{
get
{
return status.ToString();
}
}
}
And here's my take on the view:
<TextBlock x:Name="StatusBlock" HorizontalAlignment="Left" Margin="10,450,0,0" TextWrapping="Wrap" Text="{Binding StatusDisplay, Source=ReviewsControl, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Height="40" Width="205"/>
The code above doesn't even show anything, let alone do that dynamically. What should I change if I want XAML to detect changes in the in my C# code and change the textbox accordingly?
I too had issues early on. Your view (display to end-users) does not care how or where things come from, you just know what will be exposed in your View Model controller to bind to. To have things updated in your view, the most common is binding and having your view model include the INotifyPropertyChanged interface. This is so you can force raising the event when a property changes, whatever is "listening" for it will update itself..
Simple class, you can just grab from the : INotify, the event exposed to allow things to get registered to, and your method to actually RAISE the event to pass up stream to those listening for changes.
public class SomeBaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Once that is done, your going to expose properties in your class by making as public with getter/setters. You don't bind to a field.
public string SomeThingYouWantToExpose {get; set; }
And in your code, however you are getting data, label refresh, whatever, you would set and raise the property-changed
public void GettingSomeData()
{
// … doing something to get / prepare / whatever...
SomeThingYouWantToExpose = "some new label";
// Now raise which the view bound to this property will updated itself
RaisePropertyChanged( "SomeThingYouWantToExpose" );
}
Now, in your view, you would bind to whatever your view model object is and then the property on the class. Don't know if you specifically need the x:Name reference which basically makes this a field in your control. Not necessary in this example unless you are trying to bind other controls in same display as a result of this field..
<TextBlock Height="40" Width="205" Margin="10,450,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
DataContext="{Binding YourViewModelObjectName}"
Text="{Binding SomeThingYouWantToExpose}"
TextWrapping="Wrap" />
Hopefully these pieces within your scenario will make sense and move you forward in your project. Any additional clarification, let me know.
CLARIFICATION on the DATA CONTEXT BINDING
The way I have implemented in my apps, I would have a
MyView -- via the visual declaration... be it a window, grid, complex user control with many controls, whatever...
MyDataModel - a class that is used to query data from whatever data source, such as SQL-Server.
MyView_ViewModel - a custom class that has the INotifyPropertyChanged incorporated and where I expose different properties and other objects I want to expose / make available to the view
So, in the MyData_ViewModel, I would create the view and also create my view model. After creating the view, I would set the overall view's DataContext to the "MyView_ViewModel"
public class MyData_ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void LoadingMyDataAndView()
{
// Controller I use to get whatever data I need from ex: SQL
_myDataModel = new MyDataModel();
// the view / window / control I want to present to users
_myView = new MyView();
// Now, set the data context on the view to THIS ENTIRE OBJECT.
// Now, anything on THIS class made as public can be have a binding
// directly to the control in the view. Since the DataContext is
// set here, I can bind to anything at this level or lower that is public.
_myView.DataContext = this;
}
private MyView _myView;
private MyDataModel _myDataModel;
// such as example exposed property
public string SomeThingYouWantToExpose {get; set; }
public void GettingSomeData()
{
var something = _myDataModel.GetSomeData();
// … doing something to get / prepare / whatever...
SomeThingYouWantToExpose = "some new label";
// Now raise which the view bound to this property will updated itself
RaisePropertyChanged( "SomeThingYouWantToExpose" );
}
}
Hopefully this EXAMPLE shows how the pieces tie together. The view would no longer need the individual DataContext set since the whole view is set, just needs to bind to the individual public property.
Assuming that the TextBlock is a child element of the UserControl, i.e. that
<TextBlock x:Name="StatusBlock" Text="{Binding StatusDisplay}" ... />
is declared in the UserControl's XAML, the Binding's RelativeSource should be set to the parent UserControl like this:
<TextBlock Text="{Binding StatusDisplay,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Since StatusDisplay is a property of a UserControl, i.e. a DependencyObject, it should be declared as a dependency property:
public partial class ReviewsControl : UserControl
{
public static readonly DependencyProperty StatusDisplayProperty =
DependencyProperty.Register(
nameof(StatusDisplay), typeof(string), typeof(ReviewsControl);
public string StatusDisplay
{
get { return (string)GetValue(StatusDisplayProperty); }
set { SetValue(StatusDisplayProperty, value); }
}
}
Okay. So what I need to do is to initialize a ViewModel using a constructor. The problem is I can't create the constructor due lack of knowledge. I'm new to MVVM (or c# in general for that matter) and had to get some help to implement this code:
public class ViewModel
{
private static ViewModel instance = new ViewModel();
public static ViewModel Instance
{
get
{
return instance;
}
}
}
However, I fail to create a constructor to place this code.
DataContext = ViewModel.Instance
It is meant to go into two different pages to pass a value between TextBoxes.
I'm also confused as to whether I should put the ViewModel in both the main window and the page or in just one of the two.
So, anyone can help?
Follow this pattern:
This part is how your model classes should look like,
Even if you use entity framework to create your model they inherit INPC.. so all good.
public class Model_A : INotifyPropertyChanged
{
// list of properties...
public string FirstName {get; set;}
public string LastName {get; set;}
// etc...
}
each view model is a subset of information to be viewed, so you can have many view models for the same model class, notice that in case your make the call to the parameter-less c-tor you get auto instance of a mock model to be used in the view model.
public class ViewModel_A1 : INotifyPropertyChanged
{
public Model_A instance;
public ViewModel()
{
instance = new instance
{ //your mock value for the properties..
FirstName = "Offer",
LastName = "Somthing"
};
}
public ViewModel(Model_A instance)
{
this.instance = instance;
}
}
And this is for your view, if you view in the ide designer you will have a mock view model to show.
public class View_For_ViewModelA1
{
public View_For_ViewModel_A1()
{
//this is the generated constructor already implemented by the ide, just add to it:
DataContext = new ViewModel_A1();
}
public View_For_ViewModel_A1(ViewModel_A1 vm)
{
DataContext = vm;
}
}
XAML Side:
<Window x:Class="WpfApplication1.View_For_ViewModel_A1"
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:ViewModel="clr-namespace:WpfApplication1"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance ViewModel:ViewModel_A1, IsDesignTimeCreatable=True}"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
</Grid>
</Window>
In a more advanced scenario you would want to have a single view model class to relate to several model classes.. but you always should set a view to bind to a single view model.
if you need to kung-fu with your code - make sure you do that in your view model layer.
(i.e. creating a view-model that have several instances of different model types)
Note: This is not the complete pattern of mvvm.. in the complete pattern you can expose command which relate to methods in your model via your view-model and bind-able to your view as well.
Good luck :)
I basically follow this pattern:
public class ViewModelWrappers
{
static MemberViewModel _memberViewModel;
public static MemberViewModel MemberViewModel
{
get
{
if (_memberViewModel == null)
_memberViewModel = new MemberViewModel(Application.Current.Resources["UserName"].ToString());
return _memberViewModel;
}
}
...
}
To bind this to a page is:
DataContext = ViewModelWrappers.MemberViewModel;
And if I'm using more than 1 ViewModel on the page I just bind to the wrapper.
DataContext = ViewModelWrappers;
If you or anybody else, who's new to the MVVM, gets stuck here, for example at the "INotifyPropertyChanged could not be found". I recommend trying some example-MVVM's or tutorials.
Some I found useful:
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
https://www.youtube.com/watch?v=EpGvqVtSYjs&index=1&list=PL356CA0B2C8E7548D
I am trying to come up with a good way of implementing the MVVM pattern using Entity-Framework where my entities are my models. My DataContext is my viewmodel. This is a small reproduction of the problem.
View
<TextBox Text="{Binding MyText}" />
ViewModel:
I have the requirement of needing to navigate record by record from my DB. When a button is clicked in the View a command is sent to the Viewmodel that executes nextRecord(). EF does its magic and _myObject is the next row/record from the database
public class myViewModel: INotifyPropertyChanged
{
private MyEntityObject _myObject;
public string MyText
{
get { return _myObject.MyText; }
set
{
if (_myObject.MyText != value)
{
_myObject.MyText = value;
OnPropertyChanged("MyText");
}
}
}
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord() //pseudocode
}
}
Autogenerated Entity Model
public partial class MyEntityObject
{
public string MyText { get; set; }
}
Since the View has no knowledge of _myObject changing, it doesn't update when _myObject changes. A few approaches I have thought of.
I haven't tested wrapping my entities in a INotifyPropertyChanged wrapper class but am wary to do this as I have a lot of entity objects.
I could call OnPropertyChanged("...") for all properties, but some of my entities have a lot of properties to them, which would be ugly. Possible to use reflection to make it cleaner, but I may have properties that aren't databound.
I might be able to defer this to the UI, somehow refreshing the bindings when I click "Next Record", but this breaks MVVM and looks dirty
How can I get the UI to recognize changes from _myObject?
As I've mentioned in the comments, calling OnPropertyChanged("") or OnPropertyChanged(null) invalidates all properties and is equivalent to calling OnPropertyChanged for each and every property. This behavior is also documented here:
The PropertyChanged event can indicate all properties on the object
have changed by using either null or String.Empty as the property name
in the PropertyChangedEventArgs.
This means that you can simply add a call to OnPropertyChanged("") when you update your object to force WPF to reevaluate all bindings to your view model:
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord();
OnPropertyChanged("");
}
That being said, I'd still go with #Anand's solution (+1). There's an ongoing debate on whether it's OK or not for the viewmodel to expose the model as a property, and I tend to go with exposing it until you need to introduce some view model specific logic. Most of the time you won't have to and it's not worth the trouble of wrapping model properties.
The problem with your code is that when _myObject changes the MyText property changed event is not fired. A work around would be to create a new property to hold you entity
and make this property as your Grids DataContext in your view as shown below. Now when this line is executed MyObject = myEntitiesObject.NextRecord() your view will be notified about the change.
public class myViewModel : INotifyPropertyChanged
{
private MyEntityObject _myObject;
public MyEntityObject MyObject
{
get { return _myObject; }
set {
if (_myObject != value)
{
_myObject = value;
OnPropertyChanged("MyObject");
}
}
}
private void _nextRecord()
{
MyObject = myEntitiesObject.NextRecord() //pseudocode
}
}
View:
<Grid DataContext="{Binding MyObject}">
<TextBlock Text="{Binding MyText}"/>
</Grid>
An extremely simple but not very elegant solution that I believe would meet needs: upon switching records, set the DataContext to null, then back to the ViewModel.
However, there are arguably more elegant alternatives that require more work to meet all requirements. See Anand's answer for an improvement upon this.
The tag in View should have the mode and UpdateSourceTrigger attribute set with values.
I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.
The following code is a refactoring of my previous MVVM approach (Fat Models, skinny ViewModels and dumb Views, the best MVVM approach?) in which I moved the logic and INotifyPropertyChanged implementation from the model back up into the ViewModel. This makes more sense, since as was pointed out, you often you have to use models that you either can't change or don't want to change and so your MVVM approach should be able to work with any model class as it happens to exist.
This example still allows you to view the live data from your model in design mode in Visual Studio and Expression Blend which I think is significant since you could have a mock data store that the designer connects to which has e.g. the smallest and largest strings that the UI can possibly encounter so that he can adjust the design based on those extremes.
Questions:
I'm a bit surprised that I even have to "put a timer" in my ViewModel since it seems like that is a function of INotifyPropertyChanged, it seems redundant, but it was the only way I could get the XAML UI to constantly (once per second) reflect the state of my model. So it would be interesting to hear anyone who may have taken this approach if you encountered any disadvantages down the road, e.g. with threading or performance.
The following code will work if you just copy the XAML and code behind into a new WPF project.
XAML:
<Window x:Class="TestMvvm73892.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMvvm73892"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider
x:Key="DataSourceCustomer"
ObjectType="{x:Type local:CustomerViewModel}"
MethodName="GetCustomerViewModel"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource DataSourceCustomer}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
</StackPanel>
</DockPanel>
</Window>
Code Behind:
using System;
using System.Windows;
using System.ComponentModel;
using System.Threading;
namespace TestMvvm73892
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
//view model
public class CustomerViewModel : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private DateTime _timeOfMostRecentActivity;
private Timer _timer;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
this.RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
this.RaisePropertyChanged("LastName");
}
}
public DateTime TimeOfMostRecentActivity
{
get
{
return _timeOfMostRecentActivity;
}
set
{
_timeOfMostRecentActivity = value;
this.RaisePropertyChanged("TimeOfMostRecentActivity");
}
}
public CustomerViewModel()
{
_timer = new Timer(CheckForChangesInModel, null, 0, 1000);
}
private void CheckForChangesInModel(object state)
{
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, this);
}
public static CustomerViewModel GetCustomerViewModel()
{
CustomerViewModel customerViewModel = new CustomerViewModel();
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, customerViewModel);
return customerViewModel;
}
public static void MapFieldsFromModeltoViewModel
(Customer model, CustomerViewModel viewModel)
{
viewModel.FirstName = model.FirstName;
viewModel.LastName = model.LastName;
viewModel.TimeOfMostRecentActivity = model.TimeOfMostRecentActivity;
}
public static Customer GetCurrentCustomer()
{
return Customer.GetCurrentCustomer();
}
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
//model
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime TimeOfMostRecentActivity { get; set; }
public static Customer GetCurrentCustomer()
{
return new Customer
{ FirstName = "Jim"
, LastName = "Smith"
, TimeOfMostRecentActivity = DateTime.Now
};
}
}
}
I like your sample above, I think it implements the spirit of MVVM. Just for clarification, though, the ViewModel code and the Model code should not be in the same source file as the actual Code Behind. In fact, I would argue that they should not be in the same project.
Here is MVVM as I understand it:
M - the Model is the data returned from the Business Layer (BL). This should be lightweight, containing read-only data. The Model classes are dumb and do not contain Update, Write, or Delete logic, and are generated by the BL as the result of requests, commands, actions, etc. The Model classes have no knowledge of the presentation needs of the consuming application, so they can be used by any manner of application. To really take advantage of this reusability, we want the Model classes to be independent of the UI project.
VM - the ViewModel contains the communications layer: it issues the requests to the BL and processes the results in a manner fit for presentation. Like the example above, it also receives the Model and reformats it for the particular presentation needs. Think of this as a "Binding Class". In the example above, the data is simply being moved from one object to the next, but the ViewModel would be responsible for such things as exposing a "FullName" type property or adding leading zeroes to a ZipCode. It is correct that the Binding Class is the one to implement INotifyPropertyChanged. And again, for reusability I would probably extract this layer into its own project as well. This would allow you to experiment with various UI options with no plumbing changes.
V - the View is bound to the Binding class object created in the VM. The View is super dumb: it knows nothing of the BL or the VM. Data can be bound in both directions, but the VM handles errors, validation, etc. Any data synchronization operations are handled by passing requests back the the BL, and again processing the results.
It would depend on the type of application, but it seems heavy handed to constantly check the Model to see if it has changed. Pretend you were connecting to a BL that built the Business Object(BO) from a DAL, which is connecting to a DB. In this scenario, you would be constantly recreating the BO, which I'm sure would be a performance killer. You could implement a checkout system on the BL that listened for notifications, or have a method for comparing last known time of change to actual, or you could cache the BO on the BL. Just some ideas.
Also, I said above that the Model should be Lightweight. There are heavyweight options, like CSLA, but I'm not sure how well they fit into the MVVM idea.
I don't mean to pass myself off as an expert, I have only been studying these ideas so far while designing our new software's architecture. I'd love to read some discussion about this topic.
My personal opinion is that while Model should be used to load and store data, ViewModel's responsibility is to know when this data is needed, thus using timer in a ViewModel makes sense. This way you can use your Model with different ViewModel (for which it may be sufficient to retrieve data only once, not every second).
Few things to consider:
Implement your model to support
Asynchronous data retrieval (very
important if you want to target Silverlight)
Be careful about updating collection from background thread (not a problem in your example, but if you ever need to use ObservableCollection than remember that it cannot be updated from non UI thread, read more here )