UPDATE 4-6-2009: An article about the latest version of this program can be found here. The viewmodel described in this article has changed a lot.
I'm just starting with wpf and I read Beth Massi's code that belonged to this video. It's about creating a master/detail view in wpf. I also saw Jason Dollingers's video about creating a viewmodel: Jason Dolinger on Model-View-ViewModel I though I could give this viewmodel thing a try using as a starting point Beth Massi's code.
By deriving from the generic CollectionVewModel class we can create a DetailViewModel class.
Any comments are very welcome, this is my first attempt in publishing so I would love some feedback!
The Master ViewModel
First I created a generic class called CollectionModelView:
The generic base class for all commands looks like this:public class CollectionViewModel<T> : DependencyObject //,ICollectionViewModel{protected IEnumerable<T> data;protected CollectionViewSource masterViewSource = new CollectionViewSource();public CollectionViewModel(IEnumerable<T> list):this(){Data = list;}public CollectionViewModel()
{AddCommand = new CommandAdd(this);DeleteCommand = new CommandDelete(this);PreviousCommand = new CommandPrevious(this);NextCommand = new CommandNext(this);}public IEnumerable<T> Data { get { return data; }set
{data = value;
masterViewSource.Source = data;MasterView = (BindingListCollectionView)masterViewSource.View;
}}public BindingListCollectionView MasterView{get { return (BindingListCollectionView)GetValue(MasterViewProperty); }set { SetValue(MasterViewProperty, value); }}// Using a DependencyProperty as the backing store for MasterView. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MasterViewProperty =DependencyProperty.Register("MasterView", typeof(BindingListCollectionView),typeof(CollectionViewModel<T>), new UIPropertyMetadata(null));public CollectionViewSource MasterViewSource { get { return masterViewSource; } }public T CurrentItem
{get
{return (T)GetValue(CurrentItemProperty);
}set
{SetValue(CurrentItemProperty, value);
}}// Using a DependencyProperty as the backing store for CurrentItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentItemProperty =DependencyProperty.Register("CurrentItem", typeof(T), typeof(CollectionViewModel<T>),new UIPropertyMetadata(null, new PropertyChangedCallback(OnCurrentItemChanged)));private static void OnCurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){/*CollectionViewModel<T> mv = (CollectionViewModel<T>)d;
if (mv.CurrentViewModel != null)
mv.CurrentViewModel.Item = (T)e.NewValue;*/
}//public ItemViewModel<T> CurrentViewModel { get; set; }
public ICommand AddCommand { get; private set; }public ICommand DeleteCommand { get; private set; }public ICommand PreviousCommand { get; private set; }public ICommand NextCommand { get; private set; }// Not all commands are shown because they are pretty simple
/// <summary>/// Private implementation of the Add Command/// </summary>private class CommandAdd : MasterBaseCommand<T>{public CommandAdd(CollectionViewModel<T> viewmodel): base(viewmodel) { }#region ICommand Members
public override void Execute(object parameter){_vm.MasterView.AddNew();_vm.MasterView.CommitNew();}#endregion
}/// <summary>/// Private implementation of the Delete Command/// </summary>private class CommandPrevious : MasterBaseCommand<T>{public CommandPrevious(CollectionViewModel<T> viewmodel) : base(viewmodel) { }#region ICommand Members
public override bool CanExecute(object parameter){return _vm.MasterView.CurrentPosition > 0;
}public override void Execute(object parameter){_vm.MasterView.MoveCurrentToPrevious();}#endregion
}}
public abstract class MasterBaseCommand<T> : ICommand{protected CollectionViewModel<T> _vm;public MasterBaseCommand(CollectionViewModel<T> viewModel){_vm = viewModel;}#region ICommand Members
public virtual bool CanExecute(object parameter){return true;}public event EventHandler CanExecuteChanged{add { CommandManager.RequerySuggested += value; }remove { CommandManager.RequerySuggested -= value; }}public abstract void Execute(object parameter);#endregion
}
Now you already can create your own master/detail viewmodel:
public class OrdersViewModel : CollectionViewModel<Order>{public OrdersViewModel(IEnumerable<Order> orders) :base(orders) { }
}
Create a OrdersViewModel and assigning it to the DataContext of the window. Here's an example how you can create master/detail
tables in xaml now:
<my:DataGrid Name="dataGrid1" ItemsSource="{Binding MasterView}"
SelectedValue="{Binding Path=CurrentItem, Mode=OneWayToSource}" IsSynchronizedWithCurrentItem="True" Grid.Row="1" />
<my:DataGrid Grid.Row="2" Height="Auto" Margin="0" Name="dataGrid2" VerticalAlignment="Stretch"
ItemsSource="{Binding CurrentItem.OrderDetails}"/>
In this piece of xaml code the SelectedValue property of the datagrid binds to the CurrentItem property of the viewmodel.
The second datagrid binds to the OrderDetails.
The Details ViewModel
public class DetailViewModel<V> : CollectionViewModel<V>
{
private string path;
public DetailViewModel(CollectionViewSource master, string path)
: base()
{
this.path = path;
Binding b = new Binding();
b.Source = master;
b.Path = new PropertyPath(path);
BindingOperations.SetBinding(masterViewSource, CollectionViewSource.SourceProperty, b);
MasterView = (BindingListCollectionView)masterViewSource.View;
master.View.CurrentChanged += new EventHandler(View_CurrentChanged);
}
public string Path
{
get { return path; }
}
void View_CurrentChanged(object sender, EventArgs e)
{
MasterView = (BindingListCollectionView)masterViewSource.View;
}
}
The DetailViewModel is like the CollectionViewModel except that a child table name should be assigned in the constructor.
We can now create an OrdersViewModel and an OrderDetailsViewmodel like below:
public class OrdersViewModel : CollectionViewModel<Order>
{
public OrdersViewModel(IEnumerable<Order> orders) :
base(orders) { }
}
public class OrderDetailsViewModel : DetailViewModel<OrdersViewModel>
{
public OrderDetailsViewModel(OrdersViewModel vm)
: base(vm.MasterViewSource, "OrderDetails")
{
}
}
public class DataModule
{
private OMSDataContext db = new OMSDataContext();
public OrdersViewModel Orders { get; private set; }
public OrderDetailsViewModel OrderDetails { get; private set; }
public ViewModels()
{
Orders = new OrdersViewModel(db.Orders);
OrderDetails = new OrderDetailsViewModel(Orders);
}
}All you have to do now is create a DataModule and connect it to the DataContext of the main window:
this.DataContext = new DataModule();
The xaml code for the main window now looks like this:
<Window x:Class="MasterDetailViewModel.MasterDetailWindow2"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window1" Height="302" Width="430" Loaded="Window_Loaded" xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"><Grid><Grid.RowDefinitions><RowDefinition Height="36" /><RowDefinition Height="131*" /><RowDefinition Height="95*" /></Grid.RowDefinitions><StackPanel Name="StackPanel3" Orientation="Horizontal" Grid.Row="0"><Button Height="25" Name="btnAdd" Width="Auto" Margin="3" Command="{Binding Orders.AddCommand}">Add Order</Button><Button Height="25" Name="btnDelete" Width="Auto" Margin="3" Command="{Binding Orders.DeleteCommand}">Delete Order</Button><Button Height="25" Name="btnPrevious" Width="75" Margin="3" Command="{Binding Orders.PreviousCommand}">Previous</Button><Button Height="25" Name="btnNext" Width="75" Margin="3" Command="{Binding Orders.NextCommand}">Next</Button><!-- The save button can work if I also allow the dataconnection in the ViewModeland add a save command
<Button Height="25" Name="btnSave" Width="75" Margin="3" Command="{Binding SaveCommand}">Save</Button>-->
</StackPanel><my:DataGrid Name="dataGrid1" ItemsSource="{Binding Orders.MasterView}" IsSynchronizedWithCurrentItem="True" Grid.Row="1"/><my:DataGrid Grid.Row="2" Height="Auto" Margin="0" Name="dataGrid2" VerticalAlignment="Stretch"ItemsSource="{Binding OrderDetails.MasterView}" IsSynchronizedWithCurrentItem="True"/></Grid></Window>
That's all! Creating master/details tables is now a very easy job with the the help of the two generic classes described above!
Geen opmerkingen:
Een reactie posten