Add new row by tabbing past last cell?

Oct 21, 2008 at 4:36 PM
I try to use the build in CanUserAddRow behaviour. What I would have expected is, that tabbing past the last cell would add a new row and place the cursor in the first cell. What actually happens is this:

I have build a very simple project, with a grid with 2 columns and wired up the InitializeNewItem event where I set the value of the first column to "[New]"

If I show an empty grid initially, there is one row visible and as soon as I click in the first column twice,
the first column shows "[New]" and I can edit. when I press tab the focus jumps to column 2, editing and pressing tab the focus jumps back to column 1 and so on: no new row is added!

If I hit Enter in Column 2, a new row gets added and the focus is in column 2 in the 2nd row (I would expect 1st column)

If I write something in this new cell (column 2, row 2) and then press tab, a new row is added (good!) but the cursor jumps back up to row1, column 1.

If I don´t write in the last cell, and only press tab, no new row is added and cursor jumps back to cell 1

This is all very confusing and I don´t know how to present my users with an intuitive working behaviour here. Can anybody please help me here?

Thanks very much in advance!

Joachim
Coordinator
Oct 21, 2008 at 7:17 PM
Ironically the problem seems to be the simplicity of  your project.

>>If I show an empty grid initially, there is one row visible and as soon as I click in the first column twice,
the first column shows "[New]" and I can edit. when I press tab the focus jumps to column 2, editing and pressing tab the focus jumps back to column 1 and so on: no new row is added!


The one row which is visible intially is the new row placeholder which one could edit and the object corresponding to it would be added to the ItemsSource when ever that row gets committed for first time and there by adding another new row placeholder. Pressing tab from cell of column 2 is moving to the cell of column 1 because I assume there is no other control in the window where the tab could go to and as long as one is tabbing in the same row, the row doesn't get committed (assuming that the underlying collectionview can cancel row edit which seems to be your case) and as long as the row doesn't get commintted the new row doesnt get added. 

>>If I hit Enter in Column 2, a new row gets added and the focus is in column 2 in the 2nd row (I would expect 1st column)

The behavior of enter is as expected, saying that it should move the focus to next available row maintianing in the same column.

>>If I write something in this new cell (column 2, row 2) and then press tab, a new row is added (good!) but the cursor jumps back up to row1, column 1.

As I already mentioned there is no other control to tab on to, so tabbing starts again at first cell of first row.

>>This is all very confusing and I don´t know how to present my users with an intuitive working behaviour here. Can anybody please help me here?

I am assuming that your real world app would have some other control to tab on to (or atleast the datagrid wouldnt start with no existing rows) and hence reducing the confusion. (try adding a button or text box outside datagrid and you would understand what I am trying to say).
Oct 22, 2008 at 9:52 AM

I thank you for your explanation. This helped me to understand the logic. I extended my simple project and added a text box before and after the grid and I see your point. But there is still one thing that feels wrong to me:

If I am editing the last cell in the grid and hit Tab, a new placeholder-row gets added to the grid, but the focus jumps into the textbox that is placed underneath the grid ignoring the placeholder (and the user has to click back in the grid to access the new row). So I have now 2 rows in my grid, one real and one placeholder. If I now click in the first cell again and press tab, this time the focus also moves through the placeholder cells!

So it seems the new placeholder row is added AFTER the key event (TAB) has been processed. But shouldn´t it be before?

Agreed, this way the user can always leave the grid via tab. But in my opinion it would be a good option to be able to toggle this behaviour especially in the CanUserAddRows-Mode.

Anyway, maybe it is a solution for me to place the "Add New" on a key (F2, I don´t want an Addnew button, the form should be operatable with keyboard only) and set the Focus to the first cell. But I am having problems with that too. I tried this:

 

 

protected void OnDataGridPreviewKeyDown(object sender, RoutedEventArgs args) {
    KeyEventArgs e = args as KeyEventArgs;
    if (e.Key == Key.F2) {
        this.grdMain.CommitEdit();
        DataRow row = new DataRow("[new row]");     //DataRow is my dataobject
        myList.Add(row);                                         //the grid is databound to myList
        BindingExpression b = BindingOperations.GetBindingExpression(this.grdMain, DataGrid.ItemsSourceProperty);
        b.UpdateTarget();    //I tried this to force the grid to add the new DataGridCellsPresenter for the new row, but does not work
        DataGridCell c = DataGridUtilities.GetCell(this.grdMain, myList.Count - 1, 0);
        c.Focus();
        args.Handled =
true;
    }
}

This gives me an exception in GetCell, however, since the index of myList (that already has the new row) is pointing to the placeholder row in the grid, since the new row has not yet been added.

Can you give me a hint how to achieve this?

Joachim

 

Coordinator
Oct 22, 2008 at 6:19 PM
Is your 'mylist' an ObservableCollection<DataRow>? If an ItemsControl like DataGrid needs to pickup changes to the ItemsSource collection from outside its scope, the collection assigned to ItemsSource should implement INotifyCollectionChanged. System.Collections.ObjectModel.ObservableCollection is a packaged collection class which implements this interface, so use it if possible.

Then the code would be as simple as....

protected void OnDataGridPreviewKeyDown(object sender, RoutedEventArgs args) {
    KeyEventArgs e = args as KeyEventArgs;
    if (e.Key == Key.F2) {
        DataRow row = new DataRow("[new row]");     //DataRow is my dataobject
        myList.Add(row);                                         //the grid is databound to myList

        DataGridCell c = DataGridUtilities.GetCell(this.grdMain, myList.Count - 1, 0); //Assume it takes care of virtualization
        c.Focus();
        args.Handled =
true;
    }
}

A couple of other thoughts.....
  • Try using someother key, other than F2. F2 already has a special meaning in DataGrid's context. It is to edit the cell with focus and you will lose that functionality.
  •  I assume that your DataGridUtilities.GetCell takes care of virtualization (by calling DataGrid.ScrollIntoView method).
  • You have to note that if you are adding new object to the collection then it would be your responsibility to handle the cancel operation (you wont get it for free as is the case with using our new row place holder by hitting escape)

So considering all these, if starting the edit of new row placeholder on keyboard action will satisfy your requirement then the following code may just work for you

protected void OnDataGridPreviewKeyDown(object sender, RoutedEventArgs args) {
    KeyEventArgs e = args as KeyEventArgs;
    if (e.Key == <someKey>) {
        grdMain.ScrollIntoView(CollectionView.NewItemPlaceholder, grdMain.ColumnFromDisplayIndex(0));
        DataGridCell c = DataGridUtilities.GetCell(this.grdMain, grdMain.Items.Count - 1, 0);  //assuming that the column index here is display index
        c.Focus();
        grdMain.BeginEdit();
        args.Handled =
true;
    }
}

Oct 23, 2008 at 8:49 AM
I am sorry, but the first code expamle is definitely not working. The GetCell() method that tries to access the newly added item throws an exception, because the newly added item in the ObservableCollection is not yet present in the datagrid. This is my code (CanUserAddRows=false):

private
ObservableCollection<DataRow> myList = new ObservableCollection<DataRow>();
protected void OnDataGridPreviewKeyDown(object sender, RoutedEventArgs args) {
    KeyEventArgs e = args as KeyEventArgs;
    if (e.Key == Key.F12) {
        DataRow row = new DataRow("[new row]");
        myList.Add(row);
        DataGridCell c = DataGridUtilities.GetCell(this.grdMain, grdMain.Items.Count - 1, 0);//this causes an exception because the underlying datagrid does not yet contain the new row
        c.Focus();
        grdMain.BeginEdit();
        args.Handled =
true;
    }
}

 

public static DataGridCell GetCell(DataGrid grd, int row, int column) {
    DataGridRow rowContainer = GetRow(grd, row);
    if (rowContainer != null) {
        DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
        // try to get the cell but it may possibly be virtualized
        DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        if (cell == null) {
        // now try to bring into view and retreive the cell
            grd.ScrollIntoView(rowContainer, grd.Columns[column]);
            cell = (
DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        }
        return cell;
    }
    return null;
}

 


Also, the second example has a flaw: If I am in the placeholder row and type some values, now I want to add a new row and hit F12 (in my case) nothing happens visually. The grid does the commit of the current (placholder) row (which actually should convert it in a real row, yes?), but obviously the current row is still treated as the placeholder. How can I trigger the addition of a new placeholder row without leaving the current row? Here is my code: (CanUserAddRows = true)

 

protected void OnDataGridKeyDown(object sender, RoutedEventArgs args) {
    KeyEventArgs e = args as KeyEventArgs;
    if (e.Key == Key.F12) {
        this.grdMain.ScrollIntoView(CollectionView.NewItemPlaceholder, grdMain.ColumnFromDisplayIndex(0));
        DataGridCell c = DataGridUtilities.GetCell(this.grdMain, grdMain.Items.Count - 1, 0);
        c.Focus();
        grdMain.BeginEdit();
        args.Handled =
true;
    }
}

 

Is there any workaround for this?
Joachim
Coordinator
Nov 2, 2008 at 4:13 PM
For the first example, you need to call DataGrid.UpdateLayout() before calling GetCell().

In the second example, you will have to check if you are in the new row, do a commit, then set focus on the new placeholder and begin edit.