Thursday, April 5, 2018

Put cell into edit mode when clicked

Most users prefer an application to immediately put a datagridcell into edit mode as soon as they click on it. The default behavior is to require one click to select the cell, another to put the cell into edit mode, and potentially a third to set focus or check the checkbox. Users don't like this.

Here is how we can put the cell into edit mode, set focus, and select the current contents all on the initial click.

Start a new WPF project called EditOnSelection and target any Framework you want.


We will create a DataGrid with different types of columns so we can see how they all work. 

<Window x:Class="EditOnSelection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EditOnSelection"
        mc:Ignorable="d" Title="EditOnSelect" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <FrameworkElement x:Key="ProxyElement"/>
    </Window.Resources>
    <Grid>
        <ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
        <DataGrid Grid.Row="1" ItemsSource="{Binding People}" AutoGenerateColumns="False" IsEnabled="True" SelectionUnit="CellOrRowHeader" SelectionMode="Single" DataGridCell.Selected="DataGrid_Selected" MouseDoubleClick="DataGrid_MouseDoubleClick" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding FirstName}" Header="First Name" Width="*"/>
                <DataGridTextColumn Binding="{Binding LastName}" Header="First Name" Width="*"/>
                <DataGridCheckBoxColumn Binding="{Binding IsRelated}" Header="Related?" Width="100"/>
                <DataGridComboBoxColumn ItemsSource="{Binding DataContext.Genders, Source={StaticResource ProxyElement}}" Header="Gender" SelectedItemBinding="{Binding Gender}" Width="100"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

In the code behind we define the collection we are binding to and some code to handle the cell selection event. Note the code that adds a line to the collection when you double-click in the body of the data grid.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace EditOnSelection
{
  
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public class cPerson
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public bool IsRelated { get; set; }
            public string Gender { get; set; }
        }

        private ObservableCollection<cPerson> _People = new ObservableCollection<cPerson>();
        public ObservableCollection<cPerson> People
        {
            get { return _People; }
            set
            {
                if (!value.Equals(_People))
                {
                    _People = value;
                    NotifyPropertyChanged("People");
                }
            }
        }

        private ObservableCollection<string> _Genders = new ObservableCollection<string>() { "Male", "Female", "Don't know" };
        public ObservableCollection<String> Genders { get { return _Genders; } }

        public MainWindow()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private void DataGrid_Selected(object sender, RoutedEventArgs e)
        {
            if (e.OriginalSource.GetType() == typeof(DataGridCell))
            {
                DataGrid g = (DataGrid)sender;
                g.BeginEdit(e);
                DataGridCell Cell = e.OriginalSource as DataGridCell;
                List<TextBox> tbl = FindChildrenByType<TextBox>(Cell);
                if (tbl.Count > 0)
                {
                    tbl[0].Focus();
                    tbl[0].SelectAll();
                }
            }
        }

        public static List<T> FindChildrenByType<T>(DependencyObject depObj) where T : DependencyObject
        {
            List<T> Children = new List<T>();
            if (depObj != null)
            {
                for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(depObj) - 1; i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        Children.Add((T)child);
                    }
                    Children.AddRange(FindChildrenByType<T>(child));
                }
            }
            return Children;
        }

        private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (e.OriginalSource.GetType() != typeof(DataGridCell))
            {
                People.Add(new cPerson());
            }
        }
    }
}

The method DataGrid_Selected is called whenever the user selects a data grid cell. It makes sure the user really did select a cell, then places the grid into edit mode. Then it finds the cell's text box (if any) and puts the focus there. Finally it pre-selects all the text in the textbox.

The result of a single mouse click is shown below.


No comments:

Post a Comment