locked
Programmatically beginning edit in DataGrid cell RRS feed

  • Question

  • Hi group, I'm trying to customize the behaviour of a data-bound WPF Toolkit DataGrid so that editing is automatically begun whenever the user changes selection. Each row contains just 3 columns and only the first is editable. I tried to handle the datagrid selectionchanged event, so that in code behind I can get the current row and then the 1st cell in it, focus it and begin edit programmatically. Anyway This works only partially as editing is begun but if I type something nothing happens as the focus isn't really there and the caret is missing from the textbox. If I manually click in the textbox I can effectively type, but the point was to avoid clicking and just let editing happen. Could anyone give a hint? here's the relevant code for the handler and its helper methods:

    DataGridCell cell = DataGridHelper.GetCell(mygrid, mygrid.SelectedIndex, 0);
    cell.Focus();            
    mygrid.BeginEdit();

    The DataGridHelper is implemented as follows:

    static class DataGridHelper
    {
        static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        } 
    
        static public DataGridCell GetCell(DataGrid dg, int row, int column)
        {
            DataGridRow rowContainer = GetRow(dg, 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
                    dg.ScrollIntoView(rowContainer, dg.Columns[column]);
                    cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                }
                return cell;
            }
            return null;
        }
    
    
        static public DataGridRow GetRow(DataGrid dg, int index)
        {
            DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
            if (row == null)
            {
                // may be virtualized, bring into view and try again
                dg.ScrollIntoView(dg.Items[index]);
                row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
            }
            return row;
        }
    }
    Saturday, November 21, 2009 1:51 PM

Answers

  • Hi Naftis,

    OK. Here's an updated sample, this one now uses DataTemplates. The trick is to wrap the TextBox in a Grid which in turn allows for using "focus-binding".

    XAML:

    <Window x:Class="WpfTests.DataGrid_AutoEditSelectedCell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
        Title="DataGrid_CopyToClipboard" Height="300" Width="500">
    
       <Window.Resources>
          <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
             <x:XData>
                <Inventory xmlns="">
                   <Books>
                      <Book ISBN="0-7356-0562-9" Stock="in" Number="9">
                         <Title>XML in Action</Title>
                         <Summary>XML Web Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1370-2" Stock="in" Number="8">
                         <Title>Programming Microsoft Windows With C#</Title>
                         <Summary>C# Programming using the .NET Framework</Summary>
                      </Book>
                      <Book ISBN="0-7356-1288-9" Stock="out" Number="7">
                         <Title>Inside C#</Title>
                         <Summary>C# Language Programming</Summary>
                      </Book>
                      <Book ISBN="0-7356-1377-X" Stock="in" Number="5">
                         <Title>Introducing Microsoft .NET</Title>
                         <Summary>Overview of .NET Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1448-2" Stock="out" Number="4">
                         <Title>Microsoft C# Language Specifications</Title>
                         <Summary>The C# language definition</Summary>
                      </Book>
                   </Books>
                   <CDs>
                      <CD Stock="in" Number="3">
                         <Title>Classical Collection</Title>
                         <Summary>Classical Music</Summary>
                      </CD>
                      <CD Stock="out" Number="9">
                         <Title>Jazz Collection</Title>
                         <Summary>Jazz Music</Summary>
                      </CD>
                   </CDs>
                </Inventory>
             </x:XData>
          </XmlDataProvider>
       </Window.Resources>
    
       <tk:DataGrid x:Name="dg"
                       AutoGenerateColumns="False" 
                       ItemsSource="{Binding Source={StaticResource InventoryData}, XPath=Book[@*]}"
                       Margin="5"
                       SelectionMode="Extended"
                       SelectionUnit="Cell"
                       ClipboardCopyMode="IncludeHeader"
                       CellEditEnding="dg_CellEditEnding"
                       >
          <tk:DataGrid.Columns>
             <tk:DataGridTemplateColumn Header="ISBN">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=@ISBN}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt1}">
                         <TextBox Name="txt1" Text="{Binding XPath=@ISBN}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
    
             <tk:DataGridTemplateColumn Header="Title">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=Title}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt2}">
                         <TextBox Name="txt2" Text="{Binding XPath=Title}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
    
             <tk:DataGridTemplateColumn Header="Summary">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=Summary}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt3}">
                         <TextBox Name="txt3" Text="{Binding XPath=Summary}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
          </tk:DataGrid.Columns>
       </tk:DataGrid>
    </Window>
    

    Code:

    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.Windows.Controls;
    using System.Windows.Input;
    
    namespace WpfTests
    {
       /// <summary>
       /// Interaction logic for DataGrid_CopyToClipboard.xaml
       /// </summary>
       public partial class DataGrid_AutoEditSelectedCell : Window
       {
          bool _fRightClicked = false;
    
          public DataGrid_AutoEditSelectedCell()
          {
             InitializeComponent();
             dg.SelectedCellsChanged += new SelectedCellsChangedEventHandler(dg_SelectedCellsChanged);
          }
    
          void dg_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
          {
             //DataGridCell cell = DataGridHelper.GetCell(dg, dg.SelectedIndex, 0);
             //cell.Focus();
             //dg.BeginEdit();
    
             DataGridCellInfo dgci = dg.SelectedCells[0];
             DataGridCell dgc = DataGridHelper.GetCell(
                   dg,
                   DataGridHelper.GetRowIndex(dg, dgci),
                   DataGridHelper.GetColIndex(dg, dgci)
                );
             dgc.IsSelected = true;
             dgc.Focus();
             dg.BeginEdit();
          }
    
          private void TextCell_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
          {
             _fRightClicked = true;
          }
    
          private void TextBox_GotFocus(object sender, RoutedEventArgs e)
          {
             TextBox tb = sender as TextBox;
             Dispatcher.BeginInvoke(new SelectAllDelegate(SelectAll), tb);
          }
    
          private delegate void SelectAllDelegate(TextBox tb);
    
          private void SelectAll(TextBox tb)
          {
             tb.SelectAll();
          }
    
          private void dg_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
          {
             if (_fRightClicked)
             {
                e.Cancel = true;
                _fRightClicked = false;
             }
          }
       }
    }
    


    Cheers,
    Olaf
    • Proposed as answer by Olaf Rabbachin Thursday, November 26, 2009 5:06 PM
    • Marked as answer by Bruce.Zhou Friday, December 4, 2009 1:40 AM
    Sunday, November 22, 2009 8:24 AM

All replies

  • Hi Naftis,

    I'm uncertain as to wheter there wouldn't be an easier approach, but here's what I can come up with.
    First, a sample window. XAML:

    <Window x:Class="WpfTests.DataGrid_AutoEditSelectedCell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
        Title="DataGrid_CopyToClipboard" Height="300" Width="500">
    
       <Window.Resources>
          <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
             <x:XData>
                <Inventory xmlns="">
                   <Books>
                      <Book ISBN="0-7356-0562-9" Stock="in" Number="9">
                         <Title>XML in Action</Title>
                         <Summary>XML Web Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1370-2" Stock="in" Number="8">
                         <Title>Programming Microsoft Windows With C#</Title>
                         <Summary>C# Programming using the .NET Framework</Summary>
                      </Book>
                      <Book ISBN="0-7356-1288-9" Stock="out" Number="7">
                         <Title>Inside C#</Title>
                         <Summary>C# Language Programming</Summary>
                      </Book>
                      <Book ISBN="0-7356-1377-X" Stock="in" Number="5">
                         <Title>Introducing Microsoft .NET</Title>
                         <Summary>Overview of .NET Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1448-2" Stock="out" Number="4">
                         <Title>Microsoft C# Language Specifications</Title>
                         <Summary>The C# language definition</Summary>
                      </Book>
                   </Books>
                   <CDs>
                      <CD Stock="in" Number="3">
                         <Title>Classical Collection</Title>
                         <Summary>Classical Music</Summary>
                      </CD>
                      <CD Stock="out" Number="9">
                         <Title>Jazz Collection</Title>
                         <Summary>Jazz Music</Summary>
                      </CD>
                   </CDs>
                </Inventory>
             </x:XData>
          </XmlDataProvider>
       </Window.Resources>
    
       <Grid>
          <Grid.RowDefinitions>
             <RowDefinition Height="*"/>
             <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <tk:DataGrid x:Name="dg"
                       AutoGenerateColumns="False" 
                       ItemsSource="{Binding Source={StaticResource InventoryData}, XPath=Book[@*]}"
                       Margin="5"
                       SelectionMode="Extended"
                       SelectionUnit="Cell"
                       ClipboardCopyMode="IncludeHeader"
                       >
             <tk:DataGrid.Columns>
                <tk:DataGridTextColumn Header="ISBN" 
                                       Binding="{Binding XPath=@ISBN}"/>
                <tk:DataGridTextColumn Header="Title" 
                                       MinWidth="200" 
                                       Binding="{Binding XPath=Title}"/>
                <tk:DataGridTextColumn Header="Summary" 
                                       MinWidth="200" 
                                       Binding="{Binding XPath=Summary}"/>
             </tk:DataGrid.Columns>
          </tk:DataGrid>
          <Button x:Name="cmdCopy" Content="_Copy to Clipboard" Grid.Row="1" 
                  Margin="5" Padding="5" HorizontalAlignment="Right"
                  Click="cmdCopy_Click"/>
       </Grid>
    </Window>
    

    Code-behind:

    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.Windows.Controls;
    
    namespace WpfTests
    {
       /// <summary>
       /// Interaction logic for DataGrid_CopyToClipboard.xaml
       /// </summary>
       public partial class DataGrid_AutoEditSelectedCell : Window
       {
          public DataGrid_AutoEditSelectedCell()
          {
             InitializeComponent();
             dg.SelectedCellsChanged += new SelectedCellsChangedEventHandler(dg_SelectedCellsChanged);
          }
    
          void dg_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
          {
             DataGridCellInfo dgci = dg.SelectedCells[0];
             DataGridCell dgc = DataGridHelper.GetCell(
                   dg,
                   DataGridHelper.GetRowIndex(dg, dgci),
                   DataGridHelper.GetColIndex(dg, dgci)
                );
             dgc.IsSelected = true;
             dgc.Focus();
             dg.BeginEdit();
          }
       }
    }
    

    The above requires that you add two new methods in your DataGridHelper class:

          static public int GetRowIndex(DataGrid dg, DataGridCellInfo dgci)
          {
             DataGridRow dgrow = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(dgci.Item);
             return dgrow.GetIndex();
          }
    
          static public int GetColIndex(DataGrid dg, DataGridCellInfo dgci)
          {
             return dgci.Column.DisplayIndex;
          }

    Using the above, the DataGrid will enter EditMode whenever a new cell is selected. I tried this with DataGridTextColumns only, though.

    Cheers,
    Olaf
    Saturday, November 21, 2009 4:58 PM
  • Thank you very much Olaf, but it does not seem to work in my case. I'm not using a DataGridTextColumn but rather a DataGridTemplateColumn, as this is required for another workaround (it seems we have to stick to a lot of them until the DataGrid guys come with a newer version of the WPF Toolkit...:( ): my XAML is like:

    <dg:DataGridTemplateColumn Header="text">
    	<dg:DataGridTemplateColumn.CellTemplate>
    		<DataTemplate>
    			<TextBlock Text="{Binding Path=Text,Mode=OneWay}"/>
    		</DataTemplate>
    	</dg:DataGridTemplateColumn.CellTemplate>
    
    	<dg:DataGridTemplateColumn.CellEditingTemplate>
    			<DataTemplate>
    				<TextBox Text="{Binding Path=Text,Mode=TwoWay}"
    						 BorderThickness="0"
    						 PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
    			</DataTemplate>
    		</dg:DataGridTemplateColumn.CellEditingTemplate>
    </dg:DataGridTemplateColumn>
    


    because this is required to avoid exiting edit mode on right click in a textbox (you can find another post of mine in this forum about this).
    Saturday, November 21, 2009 6:55 PM
  • Hi Naftis,

    OK. Here's an updated sample, this one now uses DataTemplates. The trick is to wrap the TextBox in a Grid which in turn allows for using "focus-binding".

    XAML:

    <Window x:Class="WpfTests.DataGrid_AutoEditSelectedCell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
        Title="DataGrid_CopyToClipboard" Height="300" Width="500">
    
       <Window.Resources>
          <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
             <x:XData>
                <Inventory xmlns="">
                   <Books>
                      <Book ISBN="0-7356-0562-9" Stock="in" Number="9">
                         <Title>XML in Action</Title>
                         <Summary>XML Web Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1370-2" Stock="in" Number="8">
                         <Title>Programming Microsoft Windows With C#</Title>
                         <Summary>C# Programming using the .NET Framework</Summary>
                      </Book>
                      <Book ISBN="0-7356-1288-9" Stock="out" Number="7">
                         <Title>Inside C#</Title>
                         <Summary>C# Language Programming</Summary>
                      </Book>
                      <Book ISBN="0-7356-1377-X" Stock="in" Number="5">
                         <Title>Introducing Microsoft .NET</Title>
                         <Summary>Overview of .NET Technology</Summary>
                      </Book>
                      <Book ISBN="0-7356-1448-2" Stock="out" Number="4">
                         <Title>Microsoft C# Language Specifications</Title>
                         <Summary>The C# language definition</Summary>
                      </Book>
                   </Books>
                   <CDs>
                      <CD Stock="in" Number="3">
                         <Title>Classical Collection</Title>
                         <Summary>Classical Music</Summary>
                      </CD>
                      <CD Stock="out" Number="9">
                         <Title>Jazz Collection</Title>
                         <Summary>Jazz Music</Summary>
                      </CD>
                   </CDs>
                </Inventory>
             </x:XData>
          </XmlDataProvider>
       </Window.Resources>
    
       <tk:DataGrid x:Name="dg"
                       AutoGenerateColumns="False" 
                       ItemsSource="{Binding Source={StaticResource InventoryData}, XPath=Book[@*]}"
                       Margin="5"
                       SelectionMode="Extended"
                       SelectionUnit="Cell"
                       ClipboardCopyMode="IncludeHeader"
                       CellEditEnding="dg_CellEditEnding"
                       >
          <tk:DataGrid.Columns>
             <tk:DataGridTemplateColumn Header="ISBN">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=@ISBN}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt1}">
                         <TextBox Name="txt1" Text="{Binding XPath=@ISBN}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
    
             <tk:DataGridTemplateColumn Header="Title">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=Title}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt2}">
                         <TextBox Name="txt2" Text="{Binding XPath=Title}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
    
             <tk:DataGridTemplateColumn Header="Summary">
                <tk:DataGridTemplateColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding XPath=Summary}"/>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellTemplate>
    
                <tk:DataGridTemplateColumn.CellEditingTemplate>
                   <DataTemplate>
                      <Grid FocusManager.FocusedElement="{Binding ElementName=txt3}">
                         <TextBox Name="txt3" Text="{Binding XPath=Summary}" 
                                  BorderThickness="0" GotFocus="TextBox_GotFocus"
                                  PreviewMouseRightButtonDown="TextCell_PreviewMouseRightButtonDown"/>
                      </Grid>
                   </DataTemplate>
                </tk:DataGridTemplateColumn.CellEditingTemplate>
             </tk:DataGridTemplateColumn>
          </tk:DataGrid.Columns>
       </tk:DataGrid>
    </Window>
    

    Code:

    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.Windows.Controls;
    using System.Windows.Input;
    
    namespace WpfTests
    {
       /// <summary>
       /// Interaction logic for DataGrid_CopyToClipboard.xaml
       /// </summary>
       public partial class DataGrid_AutoEditSelectedCell : Window
       {
          bool _fRightClicked = false;
    
          public DataGrid_AutoEditSelectedCell()
          {
             InitializeComponent();
             dg.SelectedCellsChanged += new SelectedCellsChangedEventHandler(dg_SelectedCellsChanged);
          }
    
          void dg_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
          {
             //DataGridCell cell = DataGridHelper.GetCell(dg, dg.SelectedIndex, 0);
             //cell.Focus();
             //dg.BeginEdit();
    
             DataGridCellInfo dgci = dg.SelectedCells[0];
             DataGridCell dgc = DataGridHelper.GetCell(
                   dg,
                   DataGridHelper.GetRowIndex(dg, dgci),
                   DataGridHelper.GetColIndex(dg, dgci)
                );
             dgc.IsSelected = true;
             dgc.Focus();
             dg.BeginEdit();
          }
    
          private void TextCell_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
          {
             _fRightClicked = true;
          }
    
          private void TextBox_GotFocus(object sender, RoutedEventArgs e)
          {
             TextBox tb = sender as TextBox;
             Dispatcher.BeginInvoke(new SelectAllDelegate(SelectAll), tb);
          }
    
          private delegate void SelectAllDelegate(TextBox tb);
    
          private void SelectAll(TextBox tb)
          {
             tb.SelectAll();
          }
    
          private void dg_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
          {
             if (_fRightClicked)
             {
                e.Cancel = true;
                _fRightClicked = false;
             }
          }
       }
    }
    


    Cheers,
    Olaf
    • Proposed as answer by Olaf Rabbachin Thursday, November 26, 2009 5:06 PM
    • Marked as answer by Bruce.Zhou Friday, December 4, 2009 1:40 AM
    Sunday, November 22, 2009 8:24 AM
  • Hi Olaf

    I tried to write some code in Grid_GotFocus event. And want to set focus on particular cell. For the I found the cell the use following line for focus.

    cell.Focus(); 

    But problem is that, it comes in editing mode (value is selected). I want in normal mode (for that I have to press Enter key to over the editing).Can you guide me how can i solve the problem?

    Monday, December 9, 2013 8:07 AM