Reputation: 16040
I have a datagrid bound to an observable collection. When the user press the "add button" it adds a new row and I do this by adding a new element to the observablecollection.
I cannot figure out how to make the newly added row with the first cell in focus as if we were editing. I am using a MVVM pattern.
Any ideas or suggestions?
Upvotes: 8
Views: 8010
Reputation: 65
Solved it in my case thanks to Jake Berger's answer.
Binding the DataGrid's SelectedItem and handling the LoadingRow event works beautifully.
e.g.:
<DataGrid SelectionUnit="FullRow" SelectionMode="Single" ItemsSource="{Binding Source=MyList}" SelectedItem="{Binding SelectedDataItem, Mode=TwoWay}" LoadingRow="MyDataGrid_LoadingRow"></DataGrid>
private void MyDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
SelectedDataItem = (MyDataObject)e.Row.Item;
}
Upvotes: 0
Reputation: 13286
The answer given by Gauss is the right approach, but with some code, it is clearer:
void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.Loaded += Row_Loaded;
}
void Row_Loaded(object sender, RoutedEventArgs e)
{
var row = (DataGridRow) sender;
row.Loaded -= Row_Loaded;
DataGridCell cell = GetCell(dataGrid, row, 0);
if (cell != null) cell.Focus();
dataGrid.BeginEdit();
}
static DataGridCell GetCell(DataGrid dataGrid, DataGridRow row, int column)
{
if (dataGrid == null) throw new ArgumentNullException("dataGrid");
if (row == null) throw new ArgumentNullException("row");
if (column < 0) throw new ArgumentOutOfRangeException("column");
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
row.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(row);
}
if (presenter != null)
{
var cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
dataGrid.ScrollIntoView(row, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
return null;
}
static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var visualChild = child as T;
if (visualChild != null)
return visualChild;
var childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
return null;
}
You retrieve the row in the DataGrid.LoadingRow
event, but the cell is not yet available. So you put an handler on the Loaded
event in order to wait for this event to occur and then you can retrieve the cell and put the focus on it.
The handler is removed to avoid a new triggering.
The editing session can also be started with dataGrid.BeginEdit()
.
All of this is in the code-behind, because it belongs to the view.
Upvotes: 4
Reputation: 1148
What worked for me was a combination of LoadingRow
, e.Row.Loaded
and the GetCell()
method of the following link:
The DataGrid.LoadingRow
event is called before the cell is available through GetCell
. But in the DaraGridRow.Loaded
event, the cell is available.
After that, you can use cell.Focus()
and DataGrid.BeginEdit()
.
Upvotes: 0
Reputation: 461
Hopes that will help others.
i'll show the MVVM way, in the View:
<DataGrid Name="dg1" ItemsSource="{Binding Table}" AutoGenerateColumns="False" Margin="3" SelectionMode="Single" IsReadOnly="True" SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="Date" Binding="{Binding mydate}" MinWidth="100"></DataGridTextColumn>
<DataGridTextColumn Header="Count" Binding="{Binding Count}" MinWidth="100"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" SourceName="dg1" >
<i:InvokeCommandAction Command="{Binding DgSelectionChanged}" CommandParameter="{Binding ElementName=dg1}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
now in the View-Model:
you need to set the SelectedIndex after : add, remove, etc...and
private ICommand dgSelectionChanged;
/// <summary>
/// when the datagrid Selection Changes
/// </summary>
public ICommand DgSelectionChanged
{
get
{
return dgSelectionChanged ??
(dgSelectionChanged = new RelayCommand<DataGrid>(dg1 =>
{
// whatever you want when the Selection Changes
SelectedIndex= d
//select the item
if (dg1.SelectedIndex > -1)
{
dg1.Focus();
dg1.CurrentCell = new DataGridCellInfo(dg1.Items[dg1.SelectedIndex], dg1.Columns[0]);
}
}));
}
}
by the way, to select the first row after control loaded, add this to the view:
<!-- select the row after control loaded -->
<i:EventTrigger EventName="Loaded" SourceName="dg1" >
<i:InvokeCommandAction Command="{Binding DgSelectionChanged}" CommandParameter="{Binding ElementName=dg1}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
thanks, Avi.
Upvotes: 0
Reputation: 8232
You want to do something like this:
DataGridCell cell = GetCell(rowIndex, colIndex);
cell.Focus;
Of course you need that elusive GetCell() method. Vincent Sibal from MSFT wrote up that method (and the required GetRow as well) in this forum post. I was searching for something else when I came across this so I haven't used it but others seem to have had good luck with it. You'll notice there are links to his blog which you might also find helpful for all things DataGrid related.
Upvotes: 0
Reputation: 5357
Try capturing the DataGrid's LoadingRow
(or similar) event. Do a SetFocus(e.Row)
(or similar) on the row. This is purely View-oriented, so it conforms to MVVM.
Upvotes: 1