5

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??

Bahman_Aries
  • 4,658
  • 6
  • 36
  • 60
Spirosi
  • 324
  • 1
  • 15

2 Answers2

1

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.

Bahman_Aries
  • 4,658
  • 6
  • 36
  • 60
  • 1
    One less but still not a single :) It will take two clicks to open dropdown. But anyway thanks, your solution suits me. – Spirosi Oct 19 '15 at 12:05
  • You are right, the first click is for selecting a new cell and the second click will open the drop-down. – Bahman_Aries Oct 19 '15 at 12:15
1

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.

Il Vic
  • 5,576
  • 4
  • 26
  • 37
  • I've got your idea. That will create new instance of TestClass in DataContext but won't open Combobox with data. As it was before you ned first to select row and only then Combobox will get data. – Spirosi Oct 24 '15 at 08:48
  • That is not right @Spirosi. I tested my code with .NET 4 and Caliburn.Micro 2.0.2; if I click on the last Combobox - the one put inside the "insert row" - it will open _**with**_ data (i.e. "First test item", "Second test item" , "Third test item" and "Fourth test item"). Do you obtain a different behaviour? – Il Vic Oct 24 '15 at 16:03
  • I've tested it with Interactivity only. Will try with Cliburn. – Spirosi Oct 24 '15 at 16:57
  • Yes you were wright, it works with Caliburn.Micro. But unfortunately i can't use it in my project. Could you try to implement same functionality with Windows.Interactivity. My attempts was unsuccessful. Would be very appreciated. – Spirosi Oct 26 '15 at 16:42
  • Wokrs fine. Thank you. – Spirosi Oct 27 '15 at 12:30