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}"
Related
The issue is that i cant seem to pass through the NavigationService to ModulesModel to use to Navigate to AnotherView items that i can only pass it to SectionModel. but i can only pass the NavigationService to the Parent ListView(Which i dont want) it needs to pass through the child ListView. HELP!!!
UI - CODE
<ListView
SeparatorColor="Transparent"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding Data}"
HasUnevenRows="True">
<ListView.Header>
<local:ModuleCourseHeaderViewControl />
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame
HasShadow="False"
BackgroundColor="{StaticResource BiancaBackgroundColour}">
<Frame.CornerRadius>
<OnIdiom
x:TypeArguments="x:Single"
Phone="0"
Tablet="15" />
</Frame.CornerRadius>
<Frame.Margin>
<OnIdiom
x:TypeArguments="Thickness"
Phone="0,10"
Tablet="20,10" />
</Frame.Margin>
<StackLayout>
<Label
FontFamily="{StaticResource LatoRegularFont}"
TextColor="{StaticResource MediumGold}"
Text="{Binding Title}">
<Label.FontSize>
<OnIdiom
x:TypeArguments="x:Double"
Phone="15"
Tablet="17" />
</Label.FontSize>
</Label>
<flv:FlowListView
BackgroundColor="Transparent"
SeparatorVisibility="None"
HasUnevenRows="False"
FlowColumnCount="{DynamicResource ModuleFlowColumnCount}"
RowHeight="{DynamicResource RiseCardHeight}"
FlowItemsSource="{Binding Modules}">
<flv:FlowListView.FlowColumnTemplate>
<OnIdiom
x:TypeArguments="DataTemplate">
<OnIdiom.Phone>
<DataTemplate>
<local:ModuleCourseCardPhoneControl>
<local:ModuleCourseCardPhoneControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardPhoneControl.GestureRecognizers>
</local:ModuleCourseCardPhoneControl>
</DataTemplate>
</OnIdiom.Phone>
<OnIdiom.Tablet>
<DataTemplate>
<local:ModuleCourseCardControl>
<local:ModuleCourseCardControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardControl.GestureRecognizers>
</local:ModuleCourseCardControl>
</DataTemplate>
</OnIdiom.Tablet>
</OnIdiom>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Models - CODE
public class NewModuleModel
{
public IList<SectionModel> Sections { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
}
public class SectionModel
{
public string Title { get; set; }
public IList<ModulesModel> Modules { get; set; }
public INavigationService NavigationService { get; set; }
}
public class ModulesModel
{
public int ModuleId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ModuleImageUrl { get; set; }
public double Progress { get; set; }
public double RoundedProgress
{
get
{
var p = Math.Round(Progress * 100, 0);
return p;
}
}
public DateTime? LastActivityDTUtc { get; set; }
public DateTime? CompletedDate { get; set; }
public string EstimatedDuration { get; set; }
public int AssetCount { get; set; }
public int? NavigationAssetID { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
public int? NavigationAssetContentType { get; set; }
public string Status
{
get
{
var result = string.Empty;
if (CompletedDate != null)
{
var c = String.Format("{0:d MMMM yyyy}", CompletedDate);
result = "Completed on " + c;
}
else if (LastActivityDTUtc != null)
{
var l = String.Format("{0:d MMMM yyyy}", LastActivityDTUtc);
result = "Last Activity on " + l;
}
else
{
result = "New";
}
return result;
}
}
public ModulesModel()
{
ItemSelectedCommand = new DelegateCommand<ModulesModel>(this.OnCardItemSelectedCommand);
}
private void OnCardItemSelectedCommand(ModulesModel module)
{
if (module.NavigationAssetContentType == 28 && module.AssetCount == 1)
{
var navigationParams = new NavigationParameters();
navigationParams.Add("AssetContentId", module.NavigationAssetID);
navigationParams.Add("AssetActivityId", module.ActiviyId);
navigationParams.Add("AssetModuleId", module.ModuleId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
navigationParams.Add("ParamModuleTitle", module.Title);
Preferences.Set("NavFromModulePage", true);
NavigationService.NavigateAsync("AssetWebViewPage", navigationParams, true, false).Forget();
}
else
{
var navigationParams = new NavigationParameters();
navigationParams.Add("ParamModuleId", module.ModuleId);
navigationParams.Add("ParamActivityId", module.ActiviyId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
Preferences.Set("NavFromModulePage", false);
//NavigationService.NavigateAsync("ModuleAssetPage", navigationParams, true, false);
}
}
public DelegateCommand<ModulesModel> ItemSelectedCommand { get; set; }
}
}
ViewModel - CODE
private async void LoadData()
{
this.ExecuteAsyncTask(async () =>
{
var result = await this.ModuleService.GetNewModuleAsync(ActivityID);
if (result != null)
{
CourseTitle = result.CourseTitle;
CourseDescription = result.CourseDescription;
CourseImageUrl = result.CourseImageUrl;
DurationInSeconds = result.DurationInSeconds;
DurationAsReadableString = result.DurationAsReadableString;
DisplayDescription = result.DisplayDescription;
Progress = Math.Round(result.Progress * 100, 0);
StartDateUTC = result.StartDateUTC;
EnrolmentDate = result.EnrolmentDate;
LastActivityDate = result.LastActivityDate;
foreach (var item in result.Sections)
{
var itemToAdd = new SectionModel
{
Title = item.Title,
Modules = item.Modules,
NavigationService = navigationService
};
Device.BeginInvokeOnMainThread(() =>
{
this.Data.Add(itemToAdd);
});
}
}
else
{
var exception = new Exception($"Api Error");
}
});
}
}
Remember that MVVM is Model-View-ViewModel. Very often we see people confusing Models and ViewModel's. I'll more indirectly answer your question by showing you the proper architecture. To start we'll look at a ToDoItem model.
public class ToDoItem
{
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime Due { get; set; }
}
With this note that we can make this easier to Bind by having our model inherit from something like Prism's BindableBase or ReactiveUI's ReactiveObject. I will make the assumption here that you already understand how to make those properties implement INotifyPropertyChanged.
The next thing we need is our ViewModel, we'll say that this is the ToDoItemsPageViewModel
public class ToDoItemsPageViewModel : BindableBase
{
public ObservableCollection<ToDoItem> Items { get; set; }
public DelegateCommand<ToDoItem> ItemSelectedCommand { get; }
private async void ItemSelectedCommandExecuted(ToDoItem item)
{
// do your navigation here..
}
}
Finally we have our ListView or CollectionView (which is what you should use as ListView is being deprecated). For simplicity let's look at the following.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding ItemSelectedCommand}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The core idea to understand here is that the ListView shares the BindingContext of its parent Page. Within our DataTemplate our BindingContext is a ToDoItem and not the ToDoItemsPageViewModel. As a result our binding for the value of Name will return the name of the individual ToDoItem. While our Binding for the CommandParameter will pass in the entire ToDoItem, our Binding for the Command will not work because the ToDoItem has no such command.
Now let's look at how we can access the Command from our ViewModel.
<ListView ItemsSource="{Binding Items}" x:Name="list">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding BindingContext.ItemSelectedCommand,Source={x:Reference list}}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What we've done here are three small changes.
We've added an x:Name to the ListView. This could also be put on the parent Page.
We've changed the Command Binding from ItemSelectedCommand to BindingContext.ItemSelectedCommand
We've changed the Command Binding to include a Source which references the parent item (the list in this case)
What this accomplishes is to update that one specific binding to look at the parent. This effectively sets the BindingContext (for this single property) to the actual parent. In this case you can think of it as the BindingContext being set to the instance of the ListView. This means that our Binding must reference the BindingContext, and then we can dot into access a property of our BindingContext on the ListView.
In my Xamarin project I have a ListView, which gets populated by an ObservableCollection, which holds "Item" objects with some properties. If I add items to the collection the UI gets updated, but if I change only a property, it won't it does nothing. Even after an UI update through adding an item does nothing, although the property gets correctly changed. How can I make the UI refresh if a property gets changed?
BindableBase is a class from PRISM that implements INotifyPropertyChanged and DelegateCommand implements ICommand, btw.
Here's my XAML:
<ListView ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ContentView Padding="0,0,0,5"
HeightRequest="50">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ItemTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<Frame OutlineColor="{Binding Color}" Padding="8">
<StackLayout Orientation="Horizontal" >
<Image x:Name="checkedImage"
HeightRequest="30"
WidthRequest="30"
BackgroundColor="{Binding Color}"
/>
<Label Text="{Binding Text}"
TextColor="{Binding Color}"
Margin="20,0,0,0"
VerticalTextAlignment="Center"
HorizontalOptions="FillAndExpand"/>
<Image Source="{Binding Image}" />
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's my ViewModel:
public class DetailPageViewModel : BindableBase
{
public DetailPageViewModel()
{
_listItems.Add(new ViewModels.Item("#123456", "Test1", "XamarinForms.Assets.Yellow.png"));
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
}
private ObservableCollection<Item> _listItems = new ObservableCollection<Item>();
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
public DelegateCommand<Item> ItemTappedCommand => new DelegateCommand<Item>(ItemTapped);
private void ItemTapped(Item listItem)
{
// Adding an item refreshes the UI.
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
// But changing the color of an item does nothing. Not even after an UI refresh.
_listItems.ElementAt(_listItems.IndexOf(listItem)).Color="#987654";
}
}
And my Item class:
public class Item
{
public string Color { set; get; }
public ImageSource Check { set; get; }
public string Text { private set; get; }
public ImageSource Image { private set; get; }
public Item(string color, string text, string imageSource)
{
Check = ImageSource.FromResource("XamarinForms.Assets.checkmark-outlined-verification-sign.png");
Color = color;
Text = text;
Image = ImageSource.FromResource(imageSource);
}
}
This is because also your item class needs to implement INotifyPropertyChanged. In your case as you are using Prism you just need to make your item class extend BindableBase (Prism base class which already implements INotifyPropertyChanged for you).
Link: https://github.com/PrismLibrary/Prism/blob/a60d38013c02b60807e9287db9ba7f7506af0e84/Source/Prism/Mvvm/BindableBase.cs
That should make it work.
Also I see in you are doing this:
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
With ObservableCollections you don't need to raise the event manually as they already do it internally. They can be defined as regular properties.
public ObservableCollection<Item> ListItems {get; set;}
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>
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.
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.