Vishal
Vishal

Reputation: 6368

Bind selected value of a multicolumn combobox inside DataGrid

Working Example :

I have a table called groups as shown below :

enter image description here

After looking at the image above I think you might have understood that primary key and foreign key exist in the same table. I think this is what developers call cyclic reference.

In MainWindow.xaml I have a DataGrid which contains three columns namely Group Name, Parent Name, Description. The xaml looks like :

<Window .......>

    <Window.DataContext>
        <self:MainWindowViewModel />
    </Window.DataContext>

    <DataGrid ItemsSource="{Binding Groups}" TabIndex="1">

        <DataGrid.Columns>

            <DataGridTemplateColumn Header="Group Name" Width="2*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding GroupName}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding GroupName}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>

            <DataGridTemplateColumn Header="Parent" Width="2*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ParentID, Converter={StaticResource GroupIDToGroupNameConverter}}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding DataContext.GroupsCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
                                  SelectedValue="{Binding ParentID}"
                                  SelectedValuePath="GroupID"
                                  DisplayMemberPath="GroupName"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>

            <DataGridTemplateColumn Header="Description" Width="2*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Description}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Description}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>

        </power:PowerDataGrid.Columns>

    </power:PowerDataGrid>

</Window>

Now I have a ViewModel called MainWindowViewModel

public class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        SampleDBContext sampleDBContext = new SampleDBContext();
        Groups = new ObservableCollection<Group>();
        GroupsCollection = new ObservableCollection<Group>(from g in sampleDBContext.Groups select g);
    }

    private ObservableCollection<Group> _groups;
    public ObservableCollection<Group> Groups
    {
        get
        {
            return _groups;
        }
        set
        {
            _groups = value;
            OnPropertyChanged("Groups");
        }
    }

    private ObservableCollection<Group> _groupsCollection;
    public ObservableCollection<Group> GroupsCollection
    {
        get
        {
            return _groupsCollection;
        }
        set
        {
            _groupsCollection = value;
            OnPropertyChanged("GroupsCollection");
        }
    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertryName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertryName));
        }
    }

    #endregion
}

GroupIDToGroupName.cs //Converter

public class GroupIDToGroupName : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null)
        {
            SampleDBContext sampleDBContext = new SampleDBContext();
            return (from g in sampleDBContext.Groups
                    where g.GroupID == (int)value
                    select g.GroupName).FirstOrDefault();
        }
        else
        {
            return "";
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        SampleDBContext sampleDBContext = new SampleDBContext();
        return (from g in sampleDBContext.Groups
                where g.GroupName == (string)value
                select g.GroupID).FirstOrDefault();
    }
}

In App.xaml :

<self:GroupIDToGroupName x:Key="GroupIDToGroupNameConveerter" />

My Case (Very similar to above sample):

I just want to use a Multi-Column ComboBox instead of simple ComboBox inside DataGrid.

I have two tables :

enter image description here

Now I have set up my code exactly as the above mentioned code.

I have added an extra class called GroupIDAndNameWithCorrespondingEffect like :

public class GroupIDAndNameWithCorrespondingEffect : INotifyPropertyChanged
{
    private int _groupID;
    public int GroupID
    {
        get
        {
            return _groupID;
        }

        set
        {
            _groupID = value;
            OnPropertyChanged("GroupID");
        }
    }

    private string _groupName;
    public string GroupName
    {
        get
        {
            return _groupName;
        }

        set
        {
            _groupName = value;
            OnPropertyChanged("GroupName");
        }
    }

    private string _correspondingEffect;
    public string CorrespondingEffect
    {
        get
        {
            return _correspondingEffect;
        }

        set
        {
            _correspondingEffect = value;
            OnPropertyChanged("CorrespondingEffect");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Changes in my ViewModel :

I removed the property GroupsCollection and all its references and added a new property called GroupIDAndNamesWithCorrespondingEffects as below :

private ObservableCollection<GroupIDAndNameWithCorrespondingEffect> _groupIDAndNamesWithCorrespondingEffects;
public ObservableCollection<GroupIDAndNameWithCorrespondingEffect> GroupIDAndNamesWithCorrespondingEffects
{
    get
    {
        return _groupIDAndNamesWithCorrespondingEffects;
    }
    set
    {
        _groupIDAndNamesWithCorrespondingEffects = value;
        OnPropertyChanged("GroupIDAndNamesWithCorrespondingEffects");
    }
}

And in the Constructor :

List<GroupIDAndNameWithCorrespondingEffect> _GroupIDAndNamesWithCorrespondingEffects = (
                                                                                                 from g in sampleDBContext.Groups
                                                                                                 select new GroupIDAndNameWithCorrespondingEffect
                                                                                                 {
                                                                                                     GroupID = g.GroupID,
                                                                                                     GroupName = g.GroupName,
                                                                                                     CorrespondingEffect = g.Effect.Effect1
                                                                                                 }
                                                                                             ).ToList();

            GroupIDAndNamesWithCorrespondingEffects
                = new ObservableCollection<GroupIDAndNameWithCorrespondingEffect>(
                                                                                _GroupIDAndNamesWithCorrespondingEffects.Where
                                                                                    (
                                                                                        u => !GetAllChildren(25)
                                                                                                .Select(x => x.GroupID)
                                                                                                .Contains(u.GroupID)
                                                                                    ).ToList()
                                                                            );

In my MainWindow.xaml I have added a resource as follows :

<Window.Resources>
    <CollectionViewSource x:Key="GroupNamesWithCorrespondingEffectsCollection" Source="{Binding GroupIDAndNamesWithCorrespondingEffects}" />    
</Window.Resources>

Inside Grid's Resources :

    <Grid.Resources>

        <CompositeCollection x:Key="Items">
            <ComboBoxItem IsEnabled="False" Background="#FF2A2A2A" Foreground="White">
                <Grid TextElement.FontWeight="Bold" >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="A" />
                        <ColumnDefinition Width="50" />
                        <ColumnDefinition SharedSizeGroup="B" />
                    </Grid.ColumnDefinitions>
                    <Grid.Children>
                        <TextBlock Grid.Column="0" Text="Group Name" />
                        <TextBlock Grid.Column="2" Text="Effect" />
                    </Grid.Children>
                </Grid>
            </ComboBoxItem>
            <CollectionContainer Collection="{Binding Source={StaticResource GroupNamesWithCorrespondingEffectsCollection}}" />
        </CompositeCollection>

        <DataTemplate DataType="{x:Type self:GroupIDAndNameWithCorrespondingEffect}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="A" />
                    <ColumnDefinition Width="50" />
                    <ColumnDefinition SharedSizeGroup="B" />
                </Grid.ColumnDefinitions>
                <Grid.Children>
                    <TextBlock Grid.Column="0" Text="{Binding GroupName}" />
                    <TextBlock Grid.Column="2" Text="{Binding CorrespondingEffect}" />
                </Grid.Children>
            </Grid>
        </DataTemplate>

    </Grid.Resources>

And I changed the ItemsSource of ComboBox to ItemsSource="{DynamicResource Items}".

Problems :

When I run the program ComboBox displays all the items correctly. Also, two columns with headers are displayed. Its working fine, but when I press Enter or TAB, then focus remains in the same cell and the comboBox's text displays namespace of GroupIDAndNameWithCorrespondingEffect

Here is the image of Problem :

enter image description here

Sample:

Incase if somebody wants to check the sample then it is available here. And database files are available here.

Upvotes: 0

Views: 1215

Answers (3)

Vishal
Vishal

Reputation: 6368

Got it!!!!

I declared the resource with key="Items" in Grid. So, when I Check for ComboBox.SelectedIndex in PreviewKeyDown event of DataGrid, it gives me -1 and so my logic works as unexpected. Also, at this time I get ComboBox.Items.Count = 0.

So I just changed the place of declaration of the resource. I mean I deleted Grid.Resources Section and in ComboBox.Resources section I wrote the same code. Now it is working fine. Now in PreviewKeyDown of DataGrid, I get the expected SelectedIndex of the ComboBox as well as ComboBox.Items.Count is equal to the Count in Source.

I don't know why this happens? As I have used it as DynamicResource I expect it to work even if it is declared in Grid.Resources section.

Upvotes: 1

SeanW
SeanW

Reputation: 36

One way to solve this is in http://www.shujaat.net/2010/08/wpf-editable-combobox-with-datatemplate.html

TextSearch.TextPath="GroupName"

Another way would be to provide a DataTemplate to the ComboBox's ItemTemplate

<ComboBox ItemTemplate="{StaticResource myDataTemplate}"/>

Upvotes: 0

Henriette van de Haar
Henriette van de Haar

Reputation: 120

Maybe ... maybe your combobox just needs IsEditable = false. IsEditable = True causes comboboxes to show namespaces instead of the text.

Upvotes: 0

Related Questions