0

I want to define the ItemsSource of an ItemsControl in XAML and in those items use data binding to the DataContext of my view, but I cannot get it to work.

Here is what a simplified version would look like:

<ComboBox DisplayMemberPath="Label" DataContext="Item 3">
    <ComboBox.ItemsSource>
        <x:Array Type="{x:Type local:Item}">
            <local:Item Label="Item 1" />
            <local:Item Label="{Binding}" />
        </x:Array>
    </ComboBox.ItemsSource>
</ComboBox>

with the Item defined like this:

public class Item : DependencyObject
{
    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.Register(nameof(Label), typeof(string), typeof(Item));

    public string Label
    {
        get { return (string)GetValue(LabelProperty); }
        set { SetValue(LabelProperty, value); }
    }
}

I am expecting to see two items in the ComboBox with "Item 1" and "Item 2" as text, but the second item has a blank text. Why?

Damien Chaib
  • 401
  • 3
  • 14
  • What's the DataContext of those Items? What is `DataContext="Item 3"`, you want the DataContext to be a string? I don't get it. Is it not feasible to have your viewmodel just expose an `ObservableCollection Items { get; }` and bind that to `ItemsSource`? – 15ee8f99-57ff-4f92-890c-b56153 Feb 02 '17 at 21:15
  • Ed beat me to it... I believe `{Binding}` will bind to the root of your DataContext, which is the object Item3. Unless Item3 is a string, you won't get a meaningful label out of it. `{Binding Label}` would bind to the Label property of Item3... – Dave Smash Feb 02 '17 at 21:16
  • This is an overly simplified example, which is why it looks stupid. In my application, the DataContext is indeed a view-model, my items are composed of a Path that represent an icon, a label, all static and defined in XAML. For two of those items, I want to add a number that is in the view-model. I could of course make an observable collection in the view-model, but I don't like the idea of setting paths to generate icons in the view-model. I really feel it belongs in the view, but maybe I'm wrong. Does it make more sense? – Damien Chaib Feb 02 '17 at 21:27
  • I don't understand what you mean about "setting paths to generate icons in the view-model" -- do you mean paths to icon resources? – 15ee8f99-57ff-4f92-890c-b56153 Feb 02 '17 at 21:35
  • Something like this: item.Path = "M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5..."; – Damien Chaib Feb 02 '17 at 21:42
  • Maybe I'm simply better off with an enum in my Item (`item.IconName = Icons.Bla;`) and use a converter in the view to map the enum value to a path... But anyway, I'm really wondering why the code I wrote doesn't work. – Damien Chaib Feb 02 '17 at 21:46
  • I would use an enum and keep the path data in the view. The converter idea sounds good. You can also create path data resources like so: `M 0,4 L 4,0 L 8,4 Z`, then use like ``. – 15ee8f99-57ff-4f92-890c-b56153 Feb 03 '17 at 14:19
  • As for the code you have, try this and look at what you see in the VS Output pane at runtime: ``. That'll cause the Binding to barf up a bunch of messages to the debug stream about each step it takes in resolving the Binding. Very handy. – 15ee8f99-57ff-4f92-890c-b56153 Feb 03 '17 at 14:20

2 Answers2

0

You want to override the ItemTemplate for the ComboBox, an example is shown below:

<ComboBox x:Name="ExampleComboBox"
          Height="24"
          Width="200"
          HorizontalContentAlignment="Stretch">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="10" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="10" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <TextBlock Grid.Column="0"
                               Text="{Binding FirstName}" />
                    <TextBlock Grid.Column="2"
                               Text="{Binding LastName}" />
                    <TextBlock Grid.Column="4"
                               HorizontalAlignment="Right"
                               Text="{Binding Country}" />
                </Grid>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>

The example output is shown below:

enter image description here

AwkwardCoder
  • 24,893
  • 27
  • 82
  • 152
0

I finally found an explanation! The ItemsSource is not in the visual tree, that's why the binding is not working. I have found a great explanation and a workaround here: https://stackoverflow.com/a/7661689, that led me to another (similar) workaround here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/.

Using the latter, my code now looks like this (in this somewhat overly simplified and rather stupide example):

<ComboBox DisplayMemberPath="Label" DataContext="Item 3">
    <ComboBox.Resources>
        <local:BindingProxy x:Key="proxy" Data="{Binding}" />
    </ComboBox.Resources>
    <ComboBox.ItemsSource>
        <x:Array Type="{x:Type local:Item}">
            <local:Item Label="Item 1" />
            <local:Item Label="{Binding Data, Source={StaticResource proxy}}" />
        </x:Array>
    </ComboBox.ItemsSource>
</ComboBox>

And it works.

Community
  • 1
  • 1
Damien Chaib
  • 401
  • 3
  • 14