I am using the view models in the class diagram below for a time sheet presentation using a DataGrid.
The top class (ActivityCollectionViewModel) is the DataContext for the grid; the collection of activities (ActivityViewModel) it holds are line items in the grid. The activity has a collection of allocations (AllocationViewModel) which are the majority of the line item DataGrid cells (columns).
Please notice that the AllocationVm (cell) has it's own command, the MakeFullDayCommand. In the current design I have equivalent commands in both the AllocationVm's parent as well as its grandparent. I did it this way thinking I could bind the grandparent's command and then use the collectionViewSource's ability to maintain the selected chiild vms so that the correct cell's command would always be the one invoked.
In practice, it is confusing to track and I am having trouble getting binding alone to keep everything synchronized, so I have resorted to several code behind hacks in the DataGrid, as shown below.
So I thought I'd step back and see if maybe someone could suggest a simpler more effective design than what I've got, or confirm that this is a viable solution and help me get a better data binding strategy in place.
Cheers,
BerrylHow it works
The bottom command in the context menu below is that nested command I am referring to.
Code Behind
This code is butt ugly and tough to test!
/// <summary>
/// Synchronize the <see cref="ActivityViewModel.Selec开发者_如何学运维tedAllocationVm"/> here so the input binding
/// key (F8) is always working on the correct command.
/// </summary>
private void OnCurrentCellChanged(object sender, EventArgs e)
{
if (sender == null) return;
var grid = (DataGrid)sender;
if (grid.CurrentColumn == null) return;
var selectedActivity = (ActivityViewModel)grid.CurrentItem;
if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn))
{
var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
var index = Convert.ToInt32(dowCol.DowIndex);
selectedActivity.SetSelectedAllocationVm(index);
}
else
{
selectedActivity.SetSelectedAllocationVm(-1);
}
}
/// <summary>
/// Invoke the MakeFullDayCommand when the user double clicks an editable cell;
/// synchronize the selected allocation view model first.
/// </summary>
private void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender == null) return;
var grid = (DataGrid)sender;
if (grid.CurrentColumn == null) return;
if (!_isEditableDayOfTheWeekColumn(grid.CurrentColumn)) return;
var selectedActivity = (ActivityViewModel) grid.CurrentItem;
var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
var index = Convert.ToInt32(dowCol.DowIndex);
var allocationVm = selectedActivity.SetSelectedAllocationVm(index);
if (allocationVm.MakeFullDayCommand.CanExecute(null))
{
allocationVm.MakeFullDayCommand.Execute(null);
}
}
/// <summary>
/// Manipululate the context menu to show the correct description of the MakeFullDayCommand.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Controls.ContextMenuEventArgs"/> instance containing the event data.</param>
void OnContextMenuOpening(object sender, ContextMenuEventArgs e) {
if (sender == null) return;
var grid = (DataGrid)sender;
if (grid.CurrentColumn == null) return;
const int INDEX_OF_MAKE_FULL_DAY_CMD = 1;
if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn)) {
var selectedActivity = (ActivityViewModel) grid.CurrentItem;
var dowCol = (DayOfTheWeekColumn) grid.CurrentColumn;
var index = Convert.ToInt32(dowCol.DowIndex);
var allocationVm = selectedActivity.SetSelectedAllocationVm(index);
var menuItem = allocationVm.MakeFullDayCommand.ToMenuItem();
if (grid.ContextMenu.Items.Count == 1) {
Log.Info("{0}", allocationVm.MakeFullDayCommand.HeaderText);
grid.ContextMenu.Items.Add(menuItem);
}
else {
var currentItem = (MenuItem) grid.ContextMenu.Items.GetItemAt(INDEX_OF_MAKE_FULL_DAY_CMD);
if (currentItem.Command != menuItem.Command) {
// remove the outdated menu item before adding back the new one
grid.ContextMenu.Items.Remove(currentItem);
grid.ContextMenu.Items.Add(menuItem);
}
}
}
else
{
if (grid.ContextMenu.Items.Count == 2)
{
// we aren't on an editable cell - remove the command altogether
grid.ContextMenu.Items.RemoveAt(INDEX_OF_MAKE_FULL_DAY_CMD);
}
}
}
In my experience with the data grid (and what seems like yours), I have had a hard time trying to get it to bind to columns by way of nested view models. Last time I tried to use it, I ended up downloading the source of the data grid and rewriting a bunch of it to support binding the way I needed. If I could start over, I would just write my own from scratch with my limited functionality.
Besides that, it may be beneficial to look at a different way of displaying your data to the end user that may work a little nicer in both user experience and coding and testability. Seems like it will be hard for a user to look at the grid and think "I should right click on the column to make a full day."
Also, part of the goodness of WPF is the ability to make a control REALLY easily. Maybe that might be a better route for you?
精彩评论