ScrollIntoView for WPF DataGrid (MVVM)

This should work. The idea is you have this attached property that you will attach to the DataGrid. In the xaml where you attach it, you'll bind it to a property on your ViewModel. Whenever you want to programmatically assign a value to the SelectedItem, you also set a value to this property, which the attached property is bound to.

I've made the attached property type to be whatever the SelectedItem type is, but honestly it doesn't matter what the type is as long as you set it to something different than what it was before. This attached property is just being used as a means to execute some code on the view control (in this case, a DataGrid) in an MVVM friendly fashion.

So, that said, here's the code for the attached property:

namespace MyAttachedProperties
{
    public class SelectingItemAttachedProperty
    {
        public static readonly DependencyProperty SelectingItemProperty = DependencyProperty.RegisterAttached(
            "SelectingItem",
            typeof(MySelectionType),
            typeof(SelectingItemAttachedProperty),
            new PropertyMetadata(default(MySelectionType), OnSelectingItemChanged));

        public static MySelectionType GetSelectingItem(DependencyObject target)
        {
            return (MySelectionType)target.GetValue(SelectingItemProperty);
        }

        public static void SetSelectingItem(DependencyObject target, MySelectionType value)
        {
            target.SetValue(SelectingItemProperty, value);
        }

        static void OnSelectingItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var grid = sender as DataGrid;
            if (grid == null || grid.SelectedItem == null)
                return;

            // Works with .Net 4.5
            grid.Dispatcher.InvokeAsync(() => 
            {
                grid.UpdateLayout();
                grid.ScrollIntoView(grid.SelectedItem, null);
            });

            // Works with .Net 4.0
            grid.Dispatcher.BeginInvoke((Action)(() =>
            {
                grid.UpdateLayout();
                grid.ScrollIntoView(grid.SelectedItem, null);
            }));
        }
    }
}

And here's the xaml snippet:

<Window ...
        xmlns:attachedProperties="clr-namespace:MyAttachedProperties">
    ...
        <DataGrid 
            attachedProperties:SelectingItemAttachedProperty.SelectingItem="{Binding MyViewModel.SelectingItem}">
            ...
        </DataGrid>
    </Grid>

The solution of @Edgar works fine, but in my application I had to check the OriginalSource of the SelectionChangedEventArgs as well.

private void OperatorQualificationsTable_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if ((OperatorQualificationsTable.SelectedItem != null) && (e.OriginalSource?.Equals(OperatorQualificationsTable) ?? false))
    {
        OperatorQualificationsTable.ScrollIntoView(OperatorQualificationsTable.SelectedItem);
    }
}

My datagrid contains following ComboBoxColumn

<dgx:EnhancedDataGridComboBoxColumn 
    DisplayMemberPath="DescriptionNL"
    Header="{x:Static nl:Strings.Label_Qualification}"
    ItemsSource="{Binding Path=QualificationKeysView, Source={StaticResource ViewModel}}"
    SelectedValueBinding="{Binding ActivityQualification.QualificationKey}"
    SelectedValuePath="QualificationKey"/>

Everytime when I scrolled up or down the selction changed event was called for the Combobox and it was no longer possible to move the selected item out of the view.


I am new to MVVM. I understand the idea of MVVM and try to implement everything correctly. I had a similar problem to above and I ended up with 1 line in XAML and 1 line in code behind. The rest of the code is in the VM. I did the following in XAML

<ListBox DockPanel.Dock="Top"
    Name="Selection1List" 
    ItemsSource="{Binding SelectedList1ItemsSource}" 
    SelectedItem="{Binding SelectedList1Item}"
    SelectedIndex="{Binding SelectedList1SelectedIndex}"
    SelectionChanged="Selection1List_SelectionChanged">

And this in the code behind:

private void Selection1List_SelectionChanged(object sender, SelectionChangedEventArgs e) {
    Selection1List.ScrollIntoView(Selection1List.SelectedItem);
}

and this works fine.

I know some people don't want even one line of code in the code behind the window. But I think this 1 line is just for the view. It has nothing to do with the data or with the logic of the data. So I would think this is no violation of the MVVM principle - and so much easier to implement.

Any comments are welcome.