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.


Wednesday, April 4, 2018

'InitializeComponent' is not declared

I started getting this compilation error after refactoring some base classes and checking my code in through TFS. I got a clean compile before the check-in, but after checking the code in, getting latest, and rebuilding, I started getting this error on certain pages but not others.


This happens when your XAML class does not match your code-behind's class or if the base class doesn't inherit from UserControl, Window, or Page. For example, if you inherit from FrameworkElement, you will see the same error plus a lot more.

<FrameworkElement x:Class="APYDirectPaymentNewAdd"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

Public Class APYDirectPaymentNewAdd
    Inherits Windows.FrameworkElement


This wasn't the case. My class ultimately inherited from UserControl. I checked three times!

After a lot of head scratching I remembered I had performed a manual merge on the project file during the check-in and realized the XAML files were now excluded from the project. I re-included them and everything worked correctly. Only some of them had been excluded which is why only a few pages showed this error.