Friday, October 14, 2022

Can't set both ContentTemplateSelector and ContentTemplate properties

Here's the requirement. In a combo box, display different values in the combo box and the drop down. Something like below, where I'm showing the stock number in the combo box but the stock number and description in the dropdown. In this case, the combo box is in an Infragistics XamDataGrid, but it could be stand-alone or in a Microsoft DataGrid.


The obvious way to do this is with an ItemTemplate

<Setter Property="ItemTemplate">
    <Setter.Value>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Width="60" Text="{Binding StockNumber}"/>
                <TextBlock Width="200" Text="{Binding Description}" TextTrimming="CharacterEllipsis"/>
            </StackPanel>
        </DataTemplate>
    </Setter.Value>
</Setter>

You specify the value to display in the combo box with DisplayMemberPath="StockNumber". But if you do this you get binding errors such as ...

System.Windows.Data Error: 25 : Both 'ContentTemplate' and 'ContentTemplateSelector' are set;  'ContentTemplateSelector' will be ignored. ComboBoxItem:'ComboBoxItem' (Name='')

WPF has got confused and thinks you're setting a ContentTemplate (which you are) and a ContentTemplateSelector (which you are not). The binding error doesn't stop the application from working correctly, but it would be nice not to see it, especially as it might mask real binding errors.

A little research on Google tells me to remove the reference to DisplayMemberPath which seems to work until you move focus off the combo box, say by clicking on the area to the right of it.


In the absence of a DisplayMemberPath WPF populated the combo box by calling the ToString method of the currently selected item. By default, this is the full class name. We got rid of the binding error, but the result is worse.

But ToString is overridable and we can override it like this.

    Public Overrides Function ToString() As String
        Return StockNumber
    End Function

To see the full demonstration of the problem and the solution, create a new Visual Studio project (WPF, VB, Framework) and call it TemplateSelectorBug. You will need to add references to the Infragistics DataPresenter and Editor assemblies. Replace MainWindow with this...

<Window x:Class="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:TemplateSelectorBug"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        xmlns:editor="http://infragistics.com/Editors"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="Template Selector Bug" Height="450" Width="800">
    <Window.Resources>
        <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
    </Window.Resources>
    <Grid>
        <ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
        <igDP:XamDataGrid DataSource="{Binding ItemDetails}" ActiveDataItem="{Binding SelectedItemDetail}">
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout>
                    <igDP:FieldLayout.Fields>
                        <igDP:ComboBoxField Label="Stock #" Name="StockNumber" Width="70" AllowEdit="True"                                       
                              ItemsSource="{Binding DataContext.StockItems, Source={StaticResource ProxyElement}}">
                            <igDP:ComboBoxField.EditorStyle>
                                <Style TargetType="editor:XamComboEditor">
                                    <Setter Property="ComboBoxStyle">
                                        <Setter.Value>
                                            <Style TargetType="ComboBox">
                                                <Setter Property="ItemTemplate">
                                                    <Setter.Value>
                                                        <DataTemplate>
                                                            <StackPanel Orientation="Horizontal">
                                                                <TextBlock Width="60" Text="{Binding StockNumber}"/>
                                                                <TextBlock Width="200" Text="{Binding Description}" TextTrimming="CharacterEllipsis"/>
                                                            </StackPanel>
                                                        </DataTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </igDP:ComboBoxField.EditorStyle>
                        </igDP:ComboBoxField>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>
    </Grid>
</Window>
 
and this...

Public Class cItemDetail
    Public Property StockNumber As String
End Class
 
Public Class cStock
    Public Property StockNumber As String
    Public Property Description As String
 
    'Public Overrides Function ToString() As String
    '    Return StockNumber
    'End Function
 
End Class
Class MainWindow
 
    Public Property ItemDetails = New List(Of cItemDetail)() From {New cItemDetail()}
    Public Property SelectedItemDetail As cItemDetail
 
    Public Property StockItems As New List(Of cStock)() From
    {
        New cStock() With {.StockNumber = "A", .Description = "Stock A"},
        New cStock() With {.StockNumber = "B", .Description = "Stock B"}
    }
 
End Class
 
Now run the application, select one of the stock numbers, and click to the right of the combo box. You will see this...



Now uncomment the ToString() method and repeat. You will see this...



It may not be the only way to solve the problem, but it works and isn't too intrusive.

If you want to, you can change the ComboBoxField declaration to see the binding error we are trying to get rid of.

<igDP:ComboBoxField Label="Stock #" Name="StockNumber" Width="70" AllowEdit="True" DisplayMemberPath="StockNumber"



No comments:

Post a Comment