Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF DataGrid Single Click to Create New Item

I have a DataGrid with DataGridTemplateColumn and ComboBox in it.

<DataGrid GridLinesVisibility="All" AutoGenerateColumns="False" ItemsSource="{Binding TestItemCollection}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*" Header="Test Column">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox Width="150"
                                  HorizontalAlignment="Left"
                                  ItemsSource="{Binding TestChildCollection}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>



public ObservableCollection<TestClass> TestItemCollection { get; set; } = new ObservableCollection<TestClass>
    {
        new TestClass(),
        new TestClass(),
        new TestClass(),
    };

    public class TestClass
    {
        public ObservableCollection<string> TestChildCollection { get; set; } = new ObservableCollection<string>
        {
            "First test item", "Second test item" , "Third test item" , "Fourth test item" 
        };
    }

When I click on the ComboBox in the blank row it apparently doesn't create a new instance of my collection and only gives a blank list.

enter image description here

I have to doubleclick on empty row space.

enter image description here

And only then I would get data in the ComboBox.

enter image description here

How can I get data in the Combobox with a single click on blank row??

like image 417
Spirosi Avatar asked Oct 31 '22 17:10

Spirosi


2 Answers

If you need to get data in the ComboBox with a single click on blank row, I suggest you to use Caliburn.Micro to "attach" a command to the DropDownOpened event of your ComboBox.

Here a sample: first of all the XAML

<Window x:Class="WpfApplication1.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="http://www.caliburnproject.org"
        Title="MainView" Height="600" Width="600"
        Name="_window">

    <DataGrid GridLinesVisibility="All" AutoGenerateColumns="False" ItemsSource="{Binding TestItemCollection}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*" Header="Test Column">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox Width="150"
                                    HorizontalAlignment="Left"
                                    ItemsSource="{Binding TestChildCollection}"
                                    cal:Message.Attach="[Event DropDownOpened] = [Action Choose($dataContext)]"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

Then the ViewModel:

public class MainViewModel : Caliburn.Micro.PropertyChangedBase
{
    public MainViewModel()
    {
        TestItemCollection = new ObservableCollection<TestClass>
        {
            new TestClass(),
            new TestClass(),
            new TestClass(),
        };
    }

    public void Choose(object data)
    {
        if (!(data is TestClass))
        {
            TestItemCollection.Add(new TestClass());
        }
    }

    public ObservableCollection<TestClass> TestItemCollection { get; set; }
}

Please consider that in my sample the TestClass code is the same that you wrote. Of course you must configure you application in order to work with Caliburn.Micro (if you do not know how to do it, you can read the documentation).

If you do not want (or maybe you cannot) use Caliburn.Micro, you can obtain the same result by using the System.Windows.Interactivity library (see my edit below).

Try the code: just a click and a new row is automatically created. I hope it can help you.

EDIT: alternative solution with System.Windows.Interactivity

If you cannot use Caliburn.Micro, you just need to modify the MainView XAML in this way:

<Window x:Class="WpfApplication1.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        Title="MainView" Height="600" Width="600"
        Name="_window">

    <DataGrid GridLinesVisibility="All" AutoGenerateColumns="False" ItemsSource="{Binding TestItemCollection}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*" Header="Test Column">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox Width="150"
                                  HorizontalAlignment="Left"
                                  ItemsSource="{Binding TestChildCollection}">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="DropDownOpened">
                                    <ei:CallMethodAction MethodName="ChooseWithInteraction"
                                                         TargetObject="{Binding ElementName=_window, Path=DataContext}" />
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

</Window>

As you can see, I just added the references to Microsoft.Expression.Interactions and System.Windows.Interactivity libraries. Then I added an EventTrigger and a CallMethodAction to the ComboBox.

Now in the MainViewModel you can replace the Choose method with the ChooseWithInteraction one (of course you can also simply add it to the code):

public void ChooseWithInteraction(object sender, EventArgs args)
{
    object data = ((ComboBox)sender).DataContext;
    if (!(data is TestClass))
    {
        TestItemCollection.Add(new TestClass());
    }
}

In this way you can obtain the same behaviour of my first solution, but without using Caliburn.

like image 137
Il Vic Avatar answered Nov 24 '22 00:11

Il Vic


I've come to a solution which you can achieve your desired functionality by following two steps.

Step One: You should specify a CellEditingTemplate for the DataGridTemplateColumn and set IsHitTestVisible to false for the DataGridTemplateColumn.CellTemplate. This will force UI to enter the edit-mode when a DataGridCell (e.g. your ComboBox) is being clicked and as a result a new instance of your collection will be created. To do so, you should first define a property in your TestClass to keep the selected value of each ComboBox:

public class TestClass
{
    public ObservableCollection<string> TestChildCollection { get; set; }

    // keeps the selected value of each ComboBox
    public string SelectedTestItem { get; set; }

    public TestClass()
    {
        TestChildCollection = new ObservableCollection<string>  {"First test item", "Second test item" , "Third test item" , "Fourth test item" };
    }

}

Then the xaml for your DataGrid should change like this:

    <DataGrid Grid.Row="0" 
              SelectionUnit="Cell"
              DataGridCell.Selected="DataGridCell_GotFocus"
              GridLinesVisibility="All" AutoGenerateColumns="False" ItemsSource="{Binding TestItemCollection}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*" Header="Test Column">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox Width="150" IsHitTestVisible="False"
                              HorizontalAlignment="Left"
                              ItemsSource="{Binding TestChildCollection}"
                              SelectedItem="{Binding SelectedTestItem}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox Width="150" 
                              HorizontalAlignment="Left"
                              ItemsSource="{Binding TestChildCollection}"
                                  SelectedItem="{Binding SelectedTestItem}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

Step Two: As you can see in the above xaml, SelectionUnit is set to Cell and also an event handler for DataGridCell.Selected has been defined. The event handler code is as below:

    private void DataGridCell_GotFocus(object sender, RoutedEventArgs e)
    {
        if (e.OriginalSource.GetType() == typeof(DataGridCell))
        {
            DataGrid dataGrid = (DataGrid)sender;
            dataGrid.BeginEdit(e);
        }
    }

This makes sure that every time you click on a DataGridCell it will enter editing-mode. therefore you need one less click each time you try to open the ComboBox inside the newly created DataGridRow.

like image 39
Bahman_Aries Avatar answered Nov 23 '22 23:11

Bahman_Aries