views:

841

answers:

2

I am trying to use the Silverlight 3.0 DataGrid with the MVVM design pattern. My page has a DataGrid and a button that adds an item to the collection in the VM using a command (from the Composite Application Library). This works fine, and the new item is displayed and selected.

The problem I can't solve is how to begin editing the row. I want the new row to be immediately editable when the user clicks the Add button i.e. focus set to the DataGrid and the new row in edit mode.

This is the XAML in the view:

<Grid x:Name="LayoutRoot">
    <StackPanel>
        <data:DataGrid ItemsSource="{Binding DataView}"/>
        <Button cmd:Click.Command="{Binding AddItemCommand}" Content="Add" />
    </StackPanel>
</Grid>

The code behind has one line of code that creates an instance of the VM and sets the DataContext of the view.

The VM code is:

public class VM 
{
    public List<TestData> UnderlyingData { get; set; }
    public PagedCollectionView DataView { get; set; }
    public ICommand AddItemCommand { get; set; }

    public VM()
    {
        AddItemCommand = new DelegateCommand<object>(o =>
            {
                DataView.AddNew();
            });

        UnderlyingData = new List<TestData>();
        UnderlyingData.Add(new TestData() { Value = "Test" });

        DataView = new PagedCollectionView(UnderlyingData);
    }
}

public class TestData
{
    public string Value { get; set; }

    public TestData()
    {
        Value = "<new>";
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

What would be the best way to solve this problem using the MVVM design pattern?

A: 

Whenever you talk about directly accessing ui components, your kinda missing the point of mvvm. The ui binds to the viewmodel, so find a way to alter the viewmodel instead.

Pierreten
Exactly. I could solve this easily if I wasn't using MVVM. My question is how to achieve the result I want while still sticking to the MVVM pattern.
bart
A: 

I faced the same issue. I've introduced interface ISupportEditingState:

public interface ISupportEditingState
{
    EditingState EditingState { get; set; }
}

My VM implements it. And then I wrote this behaviour to synchronise editing state of DataGrid and my VM:

    public class SynchroniseDataGridEditingStateBehaviour : Behavior<DataGrid>
{
    public static readonly DependencyProperty EditingStateBindingProperty =
        DependencyProperty.Register("EditingStateBinding", typeof(ISupportEditingState),
        typeof(SynchroniseDataGridEditingStateBehaviour), new PropertyMetadata(OnEditingStateBindingPropertyChange));

    private bool _attached;
    private bool _changingEditingState;

    public ISupportEditingState EditingStateBinding
    {
        get { return (ISupportEditingState)GetValue(EditingStateBindingProperty); }
        set { SetValue(EditingStateBindingProperty, value); }
    }

    private static void OnEditingStateBindingPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var b = d as SynchroniseDataGridEditingStateBehaviour;
        if (b == null)
            return;

        var oldNotifyChanged = e.OldValue as INotifyPropertyChanged;
        if (oldNotifyChanged != null)
            oldNotifyChanged.PropertyChanged -= b.OnEditingStatePropertyChanged;

        var newNotifyChanged = e.NewValue as INotifyPropertyChanged;
        if (newNotifyChanged != null)
            newNotifyChanged.PropertyChanged += b.OnEditingStatePropertyChanged;

        var newEditingStateSource = e.NewValue as ISupportEditingState;
        if (newEditingStateSource.EditingState == EditingState.Editing)
        {
            // todo: mh: decide on this behaviour once again.
            // maybe it's better to start editing if selected item is already bound in the DataGrid
            newEditingStateSource.EditingState = EditingState.LastCancelled;
        }
    }

    private static readonly string EditingStatePropertyName = 
        CodeUtils.GetPropertyNameByLambda<ISupportEditingState>(ses => ses.EditingState);

    private void OnEditingStatePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (_changingEditingState || !_attached || e.PropertyName != EditingStatePropertyName)
            return;

        _changingEditingState = true;

        var editingStateSource = sender as ISupportEditingState;
        if (editingStateSource == null)
            return;

        var grid = AssociatedObject;
        var editingState = editingStateSource.EditingState;
        switch (editingState)
        {
            case EditingState.Editing:
                grid.BeginEdit();
                break;
            case EditingState.LastCancelled:
                grid.CancelEdit();
                break;
            case EditingState.LastCommitted:
                grid.CommitEdit();
                break;
            default:
                throw new InvalidOperationException("Provided EditingState is not supported by the behaviour.");
        }

        _changingEditingState = false;
    }

    protected override void OnAttached()
    {
        var grid = AssociatedObject;
        grid.BeginningEdit += OnBeginningEdit;
        grid.RowEditEnded += OnEditEnded;
        _attached = true;
    }

    protected override void OnDetaching()
    {
        var grid = AssociatedObject;
        grid.BeginningEdit -= OnBeginningEdit;
        grid.RowEditEnded -= OnEditEnded;
        _attached = false;
    }

    void OnEditEnded(object sender, DataGridRowEditEndedEventArgs e)
    {
        if (_changingEditingState)
            return;

        EditingState editingState;
        if (e.EditAction == DataGridEditAction.Commit)
            editingState = EditingState.LastCommitted;
        else if (e.EditAction == DataGridEditAction.Cancel)
            editingState = EditingState.LastCancelled;
        else
            return; // if DataGridEditAction will ever be extended, this part must be changed
        EditingStateBinding.EditingState = editingState;
    }

    void OnBeginningEdit(object sender, DataGridBeginningEditEventArgs e)
    {
        if (_changingEditingState)
            return;
        EditingStateBinding.EditingState = EditingState.Editing;
    }
}

Works ok for me, hope it helps.

nayato
Thanks for posting the code. I can't easily test it because I'm no longer working on that project, but if anyone else finds it useful perhaps they could comment here.
bart