How do entity self-tracking with changes in VM and UI? - mvvm

my context: Entity Framework 4.0 + WCF Ria Data Service + Silverlight 4.
Suppose I have entity People from EF, then I created PeopleVM like:
public class PeopleViewModel : ViewModelBase
{
public PeopleViewModel(){
//....
this._hasChanges = MyDomainContext.HasChanges;
}
private People _People;
public People People
{
get { return _People; }
set
{
if (value != this._People)
{
value = this._People;
this.RaisePropertyChanged("People");
}
}
}
private bool _hasChanges;
public bool HasChanges
{
get { return this._hasChanges; }
set
{
if (this._hasChanges != value)
{
this._hasChanges = value;
this.RaisePropertyChanged("HasChanges");
}
}
}
//........
private RelayCommand _saveCommand = null;
public RelayCommand SaveCommand
{
//.....
}
private RelayCommand _cancelCommand = null;
public RelayCommand CancelCommand
{
//.....
}
}
in UI xaml, I set binding as:
<TextBox Text="{Binding People.FirstName, Mode=TwoWay}" />
<TextBox Text="{Binding People.LasttName, Mode=TwoWay}" />
Then I set button in UI as:
<Button Content="Save" Command="{Binding SaveCommand}" IsEnabled="{Binding HasChanges}"/>
<Button Content="Undo" Command="{Binding CancelCommand}" IsEnabled="{Binding HasChanges}"/>
So what I want is:
initially, button should be disabled because there is no data changes.
when user type some in FirstName, LastName textbox, the button should be enabled.
But with above code, even I change firstname, lastname, the button still in disable status.
How to resolve this problem?

The problem is that your viewmodel's HasChanges property is not updated when your domain context HasChanges property is updated. I typically just expose the Domain Context as a property on the view model and bind the buttons enabled state directly to its HasChanges property.
public class PeopleViewModel : ViewModelBase
{
public PeopleViewModel(DomainContext context){
this.Context = context;
}
public DomainContext Context
{ get; private set; }
private People _People;
public People People
{
get { return _People; }
set
{
if (value != this._People)
{
value = this._People;
this.RaisePropertyChanged("People");
}
}
}
//........
private RelayCommand _saveCommand = null;
public RelayCommand SaveCommand
{
//.....
}
private RelayCommand _cancelCommand = null;
public RelayCommand CancelCommand
{
//.....
}
}
Here is the XAML for the UI to bind to the context's HasChanges:
<Button Content="Save" Command="{Binding SaveCommand}" IsEnabled="{Binding Context.HasChanges}"/>
<Button Content="Undo" Command="{Binding CancelCommand}" IsEnabled="{Binding Context.HasChanges}"/>
You must make sure that the People object is pulled out of the DomainContext in order for updates to affect the HasChanges property of that DomainContext.

Related

how to bind an autocompletebox with a model in mvvm?

i exposed a collection and binded it to itemsource of autocompletebox which works but selecting or changing the text on the autocompletebox doesn't update the model like a textbox or a label!
viewmodel:
public ObservableCollection<String> SymptomsDb { get; private set; }
private String symptom;
public String Symptom
{
get { return symptom; }
set
{
symptom = value;
RaisePropertyChanged(() => this.Symptom);
}
}
public AnalysisViewModel()
{
List<String> s = new List<String>();
s.Add("test");
SymptomsDb = new ObservableCollection<String>(s);
}
view:
<controls:AutoCompleteBox
ItemsSource="{Binding SymptomsDb}"
SelectedItem="{Binding Symptom}"
Text="{Binding Symptom}"
IsTextCompletionEnabled="True"
FilterMode="Contains"/>
To get a change from the user interface back to the viewmodel, you will always need to bind the property TwoWay (except some properties like TextBox.TextProperty that are TwoWay by default):
<controls:AutoCompleteBox
ItemsSource="{Binding SymptomsDb}"
SelectedItem="{Binding Symptom, Mode=TwoWay}"
Text="{Binding Symptom}"
IsTextCompletionEnabled="True"
FilterMode="Contains"/>

BindableBase: Binding to nested property does not call setter

I have got the following Models:
public class Car : BindableBase
{
private string _model;
private string _wheels;
public string Model
{
get { return _model; }
set { SetProperty(ref _model, value); }
}
public string Wheels
{
get { return _wheels; }
set { SetProperty(ref _wheels, value); }
}
}
public class Customer : BindableBase
{
private Car _car;
public Car Car
{
get { return _car; }
set { SetProperty(ref _car, value); }
}
}
And the binding looks like this:
<Page.Resources>
<viewModels:CustomerViewModelLocator x:Key="PageViewModel" />
</Page.Resources>
<StackPanel>
<TextBox Background="AliceBlue" Text="{Binding ViewModel.Car.Model, Mode=TwoWay, Source={StaticResource PageViewModel}}"></TextBox>
<TextBox Background="AliceBlue" Text="{Binding ViewModel.Car.Wheels, Mode=TwoWay, Source={StaticResource PageViewModel}}"></TextBox>
</StackPanel>
I am using the ViewModelLocator pattern for my design time views and it looks good. But at runtime I donĀ“t hit the setters of the models.
What am I doing wrong?
Trivial types within the Customer model would be set...
That would happen if Car is null. Try setting _car = new Car() in Customer.

Xceed Datagrid loses SelectedItem when child selected

I have an Xceed datagrid in a WPF MVVM application which is set up to hold master-detail records. When a child row is selected, I want the ViewModel to detect the selected child. I would like to do this preferably with zero code-behind. I have written code which executes an action on the selected item when a contextmenu is clicked. This works correctly when a parent is selected, but always returns null when a child is selected.
I have put together a very simplified version of what I am trying to acheive:
My XAML is:
<Window x:Class="MasterDetailSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xcdg="clr-namespace:Xceed.Wpf.DataGrid;assembly=Xceed.Wpf.DataGrid.v4.2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<xcdg:DataGridCollectionViewSource x:Key="cvs_parents" Source="{Binding Path=Parents}">
<xcdg:DataGridCollectionViewSource.DetailDescriptions>
<xcdg:PropertyDetailDescription RelationName="Children"
AutoCreateDetailDescriptions="False">
</xcdg:PropertyDetailDescription>
</xcdg:DataGridCollectionViewSource.DetailDescriptions>
</xcdg:DataGridCollectionViewSource>
</Grid.Resources>
<xcdg:DataGridControl x:Name="ParentGrid"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource cvs_parents}}"
SelectedItem="{Binding SelectedItem}"
AutoCreateDetailConfigurations="True"
ReadOnly="True">
<xcdg:DataGridControl.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Execute Command"
CommandParameter="{Binding DataContext.SelectedItem}"
Command="{Binding DataContext.SampleCommand}" />
</ContextMenu>
</xcdg:DataGridControl.ContextMenu>
</xcdg:DataGridControl>
</Grid>
</Window>
The View Model is:
namespace MasterDetailSelection
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.Practices.Composite.Presentation.Commands;
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Parent> _parents;
public event PropertyChangedEventHandler PropertyChanged;
private DelegateCommand<Object> _sampleCommand;
private object _selectedItem;
public ObservableCollection<Parent> Parents
{
get { return _parents; }
set
{
_parents = value;
OnPropertyChanged("Parents");
}
}
public DelegateCommand<Object> SampleCommand
{
get
{
if (_sampleCommand == null)
{
_sampleCommand = new DelegateCommand<object>(ExecuteSampleCommand, CanExecuteSampleCommand);
OnPropertyChanged("SampleCommand");
}
return _sampleCommand;
}
}
public bool CanExecuteSampleCommand(Object commandParameter)
{
return true;
}
public void ExecuteSampleCommand(Object commandParameter)
{
Console.WriteLine("ExecuteSampleCommand");
}
public object SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public void LoadParents()
{
var parents = new ObservableCollection<Parent>()
{
new Parent()
{
Id=1,
Description = "Parent1",
Children = new List<Child>(){new Child() {Id = 1, Description = "Child1"} }
}
};
Parents = parents;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
There are 2 simple entities:
public class Parent
{
public int Id { get; set;}
public string Description { get; set;}
public IEnumerable<Child> Children { get; set;}
}
public class Child
{
public int Id { get; set; }
public string Description { get; set; }
}
The OnStartup override in App.xaml.cs contains the following:
var viewModel = new ViewModel();
var window = new MainWindow();
window.DataContext = viewModel;
viewModel.LoadParents();
window.Show();
Whenever i select the parent row, the SelectedItem setter is called with a populated object. When I selected a child row, the same setter is called, but with a null value.
Is there any way I can get a reference to the selected item when the context menu is clicked on a child row - and do this without code-behind. If not, is it possible with code-behind?
Perhaps you should set a context menu directly on the cells or row like below. Then you can send the appropriate model to the command. In the example below I use a static locator class that has my VM to bind the actual command too.
<Style TargetType="{x:Type xcdg:DataCell}">
<Setter Property="ContextMenu" Value="{StaticResource CellContextMenu}">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Execute Command"
CommandParameter="{Binding}"
Command="{Binding Source={StaticResource Locator} Path=ViewModel.SampleCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>

How to create DataTemplate in TreeView Silverlight 4.0

I have some complex requirement to create a DataTemplate for a TreeView in Silverlight. This is using MVVM design pattern.
here is what i want:
Package1
Child1
Child2
Package2
Child3
Child4
Code:
Class Package
{
string _Name;
ObservableCollection<Child> _Childs;
public ObservableCollection<Child> Childs{get{return _Childs; } set{_Childs=value;}}
public string PackageName{get{return _Name; } set{_Name=value;}}
}
class Child
{
string ChildName;
public bool IsEnabled{get;set;}
public string Id{get; set;}
}
Using the above data contract i should create a tree view:
Package Name being TreeItem header value.
Each child item is a child box with Content is ChildName and The Enabled property of the checkbox is binded to IsEnabled of Child.
I have tried the following code but it is not showing any tree view at all.
<sdk:TreeView x:Name="SelectedPackagesTV" ItemsSource="{Binding Packages, Mode=TwoWay}"
ItemTemplate="{StaticResource PackageTemplate}">
<sdk:TreeView.Resources>
<DataTemplate x:Key="FormsTemplate" >
<CheckBox Content="{Binding Id, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" >
</CheckBox>
</DataTemplate>
<sdk:HierarchicalDataTemplate x:Key="PackageTemplate" ItemsSource="{Binding Childs, Mode=TwoWay}" ItemTemplate="{StaticResource FormsTemplate}">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.Resources>
</sdk:TreeView>
NOTE: I am populating values to the PackagesCollection in the view model constructor. Will that be a problem?
Let me know if I am missing anything and also suggest me best way to resolve this.
Thanks in advance.
Edit:
The binding in the "PackageTemplate" is set to "Name" but the public property on the class is "PackageName".
Here is a sample of creating the data and attaching it. I have tested this and it works.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
public class Package
{
public string Name { get; set; }
public ObservableCollection<Child> Childs { get; set; }
}
public class Child
{
public string ChildName { get; set; }
public bool IsEnabled { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Package> _packages;
public event PropertyChangedEventHandler PropertyChanged;
public string TestStr { get { return "testing123"; } }
public ViewModel()
{
List<Package> list = new List<Package>() { new Package() { Name = "pkg1", Childs = new ObservableCollection<Child>(new List<Child>() { new Child() { ChildName = "Child1" } }) } };
this.Packages = new ObservableCollection<Package>(list);
}
public ObservableCollection<Package> Packages
{
get
{
return this._packages;
}
set
{
this._packages = value;
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
A few things:
Your business objects should look like this:
public class Package
{
public string Name { get; set; }
public ObservableCollection<Child> Childs { get; set; }
}
public class Child
{
public string ChildName { get; set; }
public bool IsEnabled { get; set; }
}
Binding can only work on public Properties, not private fields
Your xaml can be corrected to:
<sdk:TreeView ItemTemplate= "{StaticResoruce PackageTemplate}" ItemsSource="{Binding Packages}">
<sdk:TreeView.Resources>
<sdk:HierarchicalDataTemplate x:Key="PackageTemplate"
ItemsSource="{Binding Childs,Mode=TwoWay}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Name,Mode=TwoWay}" />
</sdk:HierarchicalDataTemplate>
<sdk:DataTemplate x:Key="ChildTemplate" >
<CheckBox Content="{Binding ChildName,Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}">
</CheckBox>
</sdk:DataTemplate>
</sdk:TreeView.Resources>
The templates need to be in an enclosing "Resources" bracket, either of the TreeView or the UserControl.
Your PackageTemplate should have the ItemsSource "Childs" instead of Packages. You are binding the PackageTemplate to "PackageName" but the Package class states "Name".
Not an error but because your ChildTemplate has no children of its own it only needs to be a DataTemplate, no hierarchy.
Also
ItemTemplate= {"StaticResoruce PackageTemplate"}
Should be:
ItemTemplate= "{StaticResource PackageTemplate}"

Communication between ViewModels

I have two questions regarding communication between ViewModels.
I am developing a customer management program. I'm using Laurent Bugnion's MVVM Light framework.
In the main page, there's a list of customers. when each customer is clicked, a child windows shows up with information about that customer. the user should be able to open up multiple child windows at the same time and compare information between customers. how do you pass customer object from the main page's ViewModel to the child window's ViewModel in an MVVM-friendly fashion?
In the child window that shows customer information, there are a number of tabs, each showing different areas of information. I've created separate ViewModels for each of the tabs. how can you share the current customer information between each tab's viewmodels?
Thanks a lot!
In my project I'm passing ViewModels to child windows too. I create a dependency property for the ViewModel in my child window's code behind and in the setter of this property I pass the ViewModel along to my child window's ViewModel. This means you're creating a separate ViewModel class just for your child window.
To answer your second question, you could have your child window's ViewModel contain properties that each tab cares about, but have their data context still be the same as the child window's data context so they have access to shared properties. This is actually very easy since they automatically get the child window's data context.
Here's an example illustrating the two concepts above.
The child window view DetailsWindow.xaml (note that I've gotten in the habit of naming my child window views *Window.xaml instead of *View.xaml)
<controls:ChildWindow x:Class="DetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:Views="clr-namespace:Views"
Title="Details"
DataContext="{Binding DetailsWindowViewModel, Source={StaticResource Locator}}"
>
<Grid>
<sdk:TabControl>
<sdk:TabItem Header="First Tab" Content="{Binding FirstTabContent}" />
<sdk:TabItem Header="Second Tab" Content="{Binding SecondTabContent}" />
</sdk:TabControl>
</Grid>
</controls:ChildWindow>
The child window view's code behind DetailsWindow.xaml.cs and its interface IDetailsWindow.cs
public partial class DetailsWindow : ChildWindow, IDetailsWindow
{
private IDetailsWindowViewModel ViewModel
{
get { return this.DataContext as IDetailsWindowViewModel; }
}
public DetailsWindow()
{
InitializeComponent();
}
#region Customer dependency property
public const string CustomerViewModelPropertyName = "Customer";
public ICustomerViewModel Customer
{
get
{
return (ICustomerViewModel)GetValue(CustomerViewModelProperty);
}
set
{
SetValue(CustomerViewModelProperty, value);
if (ViewModel != null)
{
ViewModel.Customer = value;
}
}
}
public static readonly DependencyProperty CustomerViewModelProperty = DependencyProperty.Register(
CustomerViewModelPropertyName,
typeof(ICustomerViewModel),
typeof(CustomerDetailsWindow),
null);
#endregion
}
public interface IDetailsWindow
{
ICustomerViewModel Customer { get; set; }
void Show();
}
The child window view model DetailsWindowViewModel.cs and its interface IDetailsWindowViewModel
public class DetailsWindowViewModel : ViewModelBase, IDetailsWindowViewModel
{
public DetailsWindowViewModel(IMessenger messenger)
: base(messenger)
{
}
#region Properties
#region Customer Property
public const string CustomerPropertyName = "Customer";
private ICustomerViewModel _customer;
public ICustomerViewModel Customer
{
get { return _customer; }
set
{
if (_customer == value)
return;
var oldValue = _customer;
_customer = value;
RaisePropertyChanged(CustomerPropertyName, oldValue, value, true);
}
}
#endregion
#region FirstTabContent Property
public const string FirstTabContentPropertyName = "FirstTabContent";
private FrameworkElement _firstTabContent;
public FrameworkElement FirstTabContent
{
get { return _firstTabContent; }
set
{
if (_firstTabContent == value)
return;
_firstTabContent = value;
RaisePropertyChanged(FirstTabContentPropertyName);
}
}
#endregion
#region SecondTabContent Property
public const string SecondTabContentPropertyName = "SecondTabContent";
private FrameworkElement _secondTabContent;
public FrameworkElement SecondTabContent
{
get { return _secondTabContent; }
set
{
if (_secondTabContent == value)
return;
_secondTabContent = value;
RaisePropertyChanged(SecondTabContentPropertyName);
}
}
#endregion
#endregion
}
public interface IDetailsWindowViewModel
{
ICustomerViewModel Customer { get; set; }
FrameworkElement FirstTabContent { get; set; }
FrameworkElement SecondTabContent { get; set; }
void Cleanup();
}
And you can show the child window from your MainPageViewModel.cs like this.
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly IDetailsWindow _detailsWindow;
public MainViewModel(IMessenger messenger, IDetailsWindow DetailsWindow)
: base(messenger)
{
_detailsWindow = DetailsWindow;
}
private void DisplayCustomerDetails(ICustomerViewModel customerToDisplay)
{
_detailsWindow.Customer = customerToDisplay;
_detailsWindow.Show();
}
}
Note that I create interfaces for all of my view models and child windows and I use an DI/IoC container in my ViewModelLocator so that all of my ViewModels' dependencies are injected for me. You don't have to do this, but I like how it works.