0

Having this structure and a combobox:

public abstract class A: UserControl
{
     public string MachineName { get; protected set; }
     ...
}

public partial class MainWindow : Window
{
     private List<A> m_listA = new List<A>();

     public MainPanel()
     {
        InitializeComponent();
        DataContext = this;
     
        cbMachines.ItemsSource = m_listA;
        cbMachines.SelectedIndex = 0;
        cbMachines.DisplayMemberPath = "MachineName";
     }
}

If I execute it I have the list of elements of the combobox perfectly filled but the selected one is empty and throws a binding error:

System.Windows.Data Error: 40 : BindingExpression path error: 'Name' property not found on 'object' 
''Grid' (Name='')'. BindingExpression:Path=Name; DataItem='Grid' (Name=''); target element is 
'TextBlock' (Name=''); target property is 'Text' (type 'String')

It seems that, it takes the grid of "A" main control as datacontext it seems that, it takes the main control as data context, but i need the usercontrol as datacontext.

How can i do this?

Ugaitz
  • 35
  • 8
  • The error message doesn't have anything to do with the code you are showing here. Besides that, having a UserControl as data item looks odd. Class A should not be derived from UserControl. – Clemens Apr 27 '21 at 11:38
  • The error is thrown by the combobox, and takes the grid as datacontext which is the first control of a class herited of A which is a Usercontrol. This was the unique way to inherit from a Usercontrol and a custom class. – Ugaitz Apr 27 '21 at 12:10
  • First, using a property named "Name" causes problem by my side. Because, Name property exists on base FrameworkElement. Second set cbMachines.IsEditable = true; With those, I didn't have any issues display selected item. – Hamit Apr 27 '21 at 21:50
  • Also I forgot to say that you should set SelectedIndex asynchronously, like that: cbMachines.Dispatcher.BeginInvoke(new Action(() => cbMachines.SelectedIndex = 0)); – Hamit Apr 27 '21 at 21:54
  • Asigning the selected index synchronously works as asynchronouly, why do I need to be async? Secondly, making it editable works for me too, but i dont want to edit it... The "Name" is not the real name im goig to change it in the question, thank you! – Ugaitz Apr 29 '21 at 09:14

1 Answers1

1

The corresponding area in default ComboBox template is this:

<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

In this visual, you cannot set SelectionBoxItemTemplate, SelectionBoxItem, SelectionBoxItemStringFormat because they are read only.

According to .NET Source code (Combobox.cs UpdateSelectionBoxItem method) it checks if current item is ContentControl. If so, it gets Content as SelectionBoxItem; ContentTemplate as SelectionBoxItemTemplate and ContentStringFormat as SelectionBoxItemStringFormat.

As UserControl derives from ContentControl; you can make it possible by setting ContentTemplate for class A like this:

<UserControl.ContentTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Parent.MachineName}" />
    </DataTemplate>
</UserControl.ContentTemplate>

Or you can use a custom template for ComboBox to assign content like this:

Content="{Binding SelectedItem.MachineName, RelativeSource={RelativeSource Mode=TemplatedParent}}"

To get the default combobox template use this.

Or you can do it on code behind:

cbMachines.ApplyTemplate();
var contentPresenter = cbMachines.Template.FindName("contentPresenter", cbMachines) as ContentPresenter;
System.Windows.Data.BindingOperations.SetBinding(contentPresenter, ContentPresenter.ContentProperty, new Binding("SelectedItem.MachineName") { Source = cbMachines });
contentPresenter.ContentTemplateSelector = null;

There is no easier way, I'm afraid.

Hamit
  • 623
  • 1
  • 8
  • 18