Question

I have a DataGrid having 3 columns.

I want to add a new row to DataGrid when user hits Enter on the last cell of DataGrid. I have successfully done everything as required using DataGrid.InputBindings, But the problem here is that when I presss Enter key on 2nd Column, a new row is added. I want it to be added when focus is on a cell which belong's to 3rd column and I press enter.

Here is my code :

<DataGrid CanUserAddRows="False" CanUserDeleteRows="True" CanUserReorderColumns="False" CanUserResizeColumns="False" AutoGenerateColumns="False" 
          ItemsSource="{Binding People}" SelectedItem="{Binding SelectedRow}" CurrentCell="{Binding SelectedCell, Mode=OneWayToSource}" 
          DataGridCell.GotFocus="DataGrid_CellGotFocus" SelectionMode="Single">
    <DataGrid.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding DataContext.NewRowCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
    </DataGrid.InputBindings>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Confirmation" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox SelectedItem="{Binding DataContext.SelectedConfirmation, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
                              Visibility="{Binding DataContext.ConfirmationVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource boolToVisibilityConverter}}">
                        <ComboBox.Items>
                            <sys:String>Add New</sys:String>
                            <sys:String>End Of List</sys:String>
                        </ComboBox.Items>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
        <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>

And In View-Model :

public class MainWindowViewModel : INotifyPropertyChanged
{

    public MainWindowViewModel()
    {
        People = new ObservableCollection<Person>();
        People.Add(new Person());

        NewRowCommand = new RelayCommand(NewRow);

    }

    private ObservableCollection<Person> _people;
    public ObservableCollection<Person> People
    {
        get
        {
            return _people;
        }
        set
        {
            _people = value;
            OnPropertyChanged("People");
        }
    }

    private Person _selectedRow;
    public Person SelectedRow
    {
        get
        {
            return _selectedRow;
        }
        set
        {
            _selectedRow = value;
            OnPropertyChanged("SelectedRow");

            if (_selectedRow == People.Last())
            {
                ConfirmationVisibility = true;
            }
            else
            {
                ConfirmationVisibility = false;
            }
        }
    }

    private bool _confirmationVisibility;
    public bool ConfirmationVisibility
    {
        get
        {
            return _confirmationVisibility;
        }
        set
        {
            _confirmationVisibility = value;
            OnPropertyChanged("ConfirmationVisibility");
        }
    }

    private string _selectedConfirmation;
    public string SelectedConfirmation
    {
        get
        {
            return _selectedConfirmation;
        }
        set
        {
            _selectedConfirmation = value;
            OnPropertyChanged("SelectedConfirmation");
        }
    }

    private DataGridCellInfo _selectedCell;
    public DataGridCellInfo SelectedCell
    {
        get
        {
            return _selectedCell;
        }
        set
        {
            _selectedCell = value;
            OnPropertyChanged("SelectedCell");
        }
    }

    public ICommand NewRowCommand { get; set; }

    private void NewRow(object obj)
    {
        if (SelectedRow == People.Last())
        {
            if (SelectedConfirmation == "Add New")
            {
                if (SelectedCell.Column.Header.ToString() == "Age")
                {
                    People.Add(new Person());
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Here is the Sample Project created in VS2012 : https://drive.google.com/file/d/0B5WyqSALui0bWVNHcVFXU1hOQ00/edit?usp=sharing

Was it helpful?

Solution

The problem with the code is with the CurrentCell property binding. it binds the new current cell before NewRowCommand is called.

Lets assume you where on "Name" column on the last row, so what happens is:

  1. You press the enter key.
  2. Current cell is updated with the new cell. (Age)
  3. Then your command is called, and uses the the new current cell(Age) and therefore adds new row.

I would suggest keeping a reference to the previous cell upon currentcell update. and using it instead of currentCell.

 public DataGridCellInfo SelectedCell
    {
        get
        {
            return _selectedCell;
        }
        set
        {
            _lastCell = _selectedCell; //here is my edit
            _selectedCell = value;
            OnPropertyChanged("SelectedCell");
        }
    }

So the new NewRow(object obj) looks like this:

 private void NewRow(object obj)
    {
        if (SelectedRow == People.Last())
        {
            if (SelectedConfirmation == "Add New")
            {
                if (_lastCell.Column !=null && _lastCell.Column.Header.ToString() =="Age")//here is my edit
                {
                    People.Add(new Person());
                }
            }
        }
    }

Also there is a problem inside DataGrid_CellGotFocus(object sender, RoutedEventArgs e). take a look at my edit, i dropped the row.IsSelected assinment.

 private void DataGrid_CellGotFocus(object sender, RoutedEventArgs e)
    {
        // Lookup for the source to be DataGridCell
        if (e.OriginalSource.GetType() == typeof(DataGridCell))
        {
            // Starts the Edit on the row;
            DataGrid grd = (DataGrid)sender;
            grd.BeginEdit(e);

            Control control = GetFirstChildByType<Control>(e.OriginalSource as DataGridCell);
            if (control != null)
            {
                control.Focus();
            }

            DataGridCell cell = GetDataGridCell(grd.CurrentCell);
            DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
            if (dataGrid != null)
            {
                if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                {
                    if (!cell.IsSelected)
                        cell.IsSelected = true;
                }
            }
        }
    }

I ran the edited code on my pc, it works great! let me know if there are any issues I might have missed.

OTHER TIPS

Any UIElement can have InputBindings, not just DataGrid. So why not move

<DataGrid.InputBindings>
    <KeyBinding Key="Enter" Command="{Binding DataContext.NewRowCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
</DataGrid.InputBindings>

To the column you would like:

    <DataGridTemplateColumn Header="Age" Width="*">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Age}">
                    <TextBlock.InputBindings>
                        <KeyBinding Key="Enter" Command="{Binding DataContext.NewRowCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                    </TextBlock.InputBindings>
                </TextBlock>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
        <DataGridTemplateColumn.CellEditingTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Age}">
                    <TextBox.InputBindings>
                        <KeyBinding Key="Enter" Command="{Binding DataContext.NewRowCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                    </TextBox.InputBindings>
                </TextBox>
            </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>
    </DataGridTemplateColumn>

When You Press enter then it instantly travels to 3rd column also So instead of creating column when you Selected header == Age create it when it equals Confirmation. Here is your modified working code to fix the error

private void NewRow(object obj)
{
    if (SelectedRow == People.Last())
    {
        if (SelectedConfirmation == "Add New")
        {
            if (SelectedCell.Column.Header.ToString() == "Confirmation")
            {
                People.Add(new Person());
            }
        }
    }
}

Just do this and your sample worked with new row being created when user presses enter on last column.
Hope you got your problem solved and earned myself my first answer and points also.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top