Vishal
Vishal

Reputation: 6378

Confusing Behavior of DataGrid

I have a DataGrid as follows:

<DataGrid CanUserAddRows="True" CanUserReorderColumns="False" CanUserSortColumns="False" CanUserDeleteRows="True"
          ItemsSource="{Binding Groups}" AutoGenerateColumns="False">
    <DataGrid.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding DataContext.NewRowCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>
    </DataGrid.InputBindings>
    <DataGrid.Resources>
        <CompositeCollection x:Key="Items">
            <ComboBoxItem IsEnabled="False" Background="#FF2A2A2A" Foreground="White">
                <Grid TextElement.FontWeight="Bold" >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="A" />
                        <ColumnDefinition Width="50" />
                        <ColumnDefinition SharedSizeGroup="B" />
                    </Grid.ColumnDefinitions>
                    <Grid.Children>
                        <TextBlock Grid.Column="0" Text="Group Name" />
                        <TextBlock Grid.Column="2" Text="Effect" />
                    </Grid.Children>
                </Grid>
            </ComboBoxItem>
            <CollectionContainer Collection="{Binding Source={StaticResource GroupNamesWithCorrespondingEffectsCollection}}" />
        </CompositeCollection>

        <DataTemplate DataType="{x:Type helpers:GroupNameWithCorrespondingEffect}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="A" />
                    <ColumnDefinition Width="50" />
                    <ColumnDefinition SharedSizeGroup="B" />
                </Grid.ColumnDefinitions>
                <Grid.Children>
                    <TextBlock Grid.Column="0" Text="{Binding GroupName}" />
                    <TextBlock Grid.Column="2" Text="{Binding CorrespondingEffect}" />
                </Grid.Children>
            </Grid>
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Name" Width="2*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding GroupName}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Group" Width="2*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{DynamicResource Items}" 
                              SelectedValue="{Binding ParentID}"
                              SelectedValuePath="GroupID" Grid.IsSharedSizeScope="True" TextSearch.TextPath="GroupName">
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Effect" Width="*" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding DataContext.Effects, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" DisplayMemberPath="Effect" 
                                                    SelectedValue="{Binding EffectID}" SelectedValuePath="EffectID"
                              Visibility="{Binding Path=DataContext.SelectedGroupID, 
                                                     RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}},
                                                     Converter={StaticResource effectsVisibilityConverter}}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Here is my GroupsViewModel to which my Page's DataContext is Bound :

public class GroupsViewModel : ViewModelBase
{
    public GroupsViewModel()
    {
        Groups = new ObservableCollection<Group>();

        NewRowCommand = new RelayCommand(NewRow);

        using (Entities db = new Entities())
        {
            List<GroupNameWithCorrespondingEffect> _GroupNamesWithCorrespondingEffects = (
                                                                                             from g in db.Groups
                                                                                             select new GroupNameWithCorrespondingEffect
                                                                                             {
                                                                                                 GroupID = g.GroupID,
                                                                                                 GroupName = g.GroupName,
                                                                                                 CorrespondingEffect = g.Master_Effects.Effect
                                                                                             }
                                                                                         ).ToList();

            GroupNamesWithCorrespondingEffects
                = new ObservableCollection<GroupNameWithCorrespondingEffect>(
                                                                                _GroupNamesWithCorrespondingEffects.Where
                                                                                    (
                                                                                        u => !StaticMethods.GetAllChildren(25)
                                                                                                .Select(x => x.GroupID)
                                                                                                .Contains(u.GroupID)
                                                                                    ).ToList()
                                                                            );

            Effects = new ObservableCollection<Master_Effects>(from m in db.Master_Effects
                                                               select m);
        }
    }

    private ObservableCollection<Group> _groups;
    public ObservableCollection<Group> Groups
    {
        get
        {
            return _groups;
        }
        set
        {
            _groups = value;
            OnPropertyChanged("Groups");
        }
    }

    public ICommand NewRowCommand { get; set; }

    private void NewRow(object obj)
    {
            Groups.Add(new Group());
    }
}

Problems :

I enter some data in the datagrid and then Press Enter a new row to the datagrid is added, which is expected. But new row is added to the top of the DataGrid instead I expect it to be added at last position. Also the data in other rows is cleared but I expect it to be as it is.

Upvotes: 2

Views: 219

Answers (2)

Maverik
Maverik

Reputation: 5681

The culprit is your KeyBinding. According to your sample project, you never save the value that's in DataGrid back in the collection when you press Enter. It normally happens on lost focus, but since your Enter adds a row before focus is lost, you add a blank row into the empty collection.

DataGrid detects the change and updates the view with this new row, while preserving your changes that are in flight (the row where you pressed Enter hasn't finished updating). The result is obviously what you see.

I'm not sure why you're using Enter as KeyBinding to add row, when this should happen by itself? If it isn't happening it is because DataGrid isn't able to create your model (perhaps it's not public? or may be you don't have default parameterless ctor defined?)

For your sample project, I removed your KeyBinding & implemented INotifyChanged on your Person model and it works.

If you're using CellTemplates, you need to implement CellEditingTemplate as well.

<DataGrid CanUserAddRows="True" CanUserDeleteRows="True" CanUserReorderColumns="False" CanUserResizeColumns="False" AutoGenerateColumns="False" ItemsSource="{Binding People}">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Name" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Name}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Age" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Age}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Age}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Upvotes: 0

David Hollinshead
David Hollinshead

Reputation: 1790

Is CanUserAddRows causing some confusion?

"When this property is set to true a blank row is displayed at the bottom of the DataGrid."

This row will always be underneath the rows provided by the ObservableCollection. I put some dummy data into NewRole like this:

var p = new Person() {Name = "New " + DateTime.Now.TimeOfDay.TotalMilliseconds};
People.Add(p);

to make the result a bit clearer. When you add a few rows the largest value of TotalMilliseconds will be at the end of the collection and will be the penultimate row in the DataGrid.

Upvotes: 1

Related Questions