3

I have a view model that contains a collection of Item Attributes, which in turn each contain a path to an image. There may be anywhere from 0 .. N item attributes in the collection.

A stackpanel in my view contains three identical image controls. Each image control is bound to the path of an item attribute image:

  • image control 1 is bound to 1st item attribute's image path (found in ItemAttributes[0].Image)
  • image control 2 is bound to 2nd item attribute's image path (found in ItemAttributes1.Image)
  • image control 3 is bound to 3rd item attribute's image path (found in ItemAttributes[2].Image)

If there are more than 3 attributes, they are ignored. In order to deal with the possibility of having 0 - 2 attributes (which means 1 or more of the image controls will be bound to null), which in turn gives the error stated in this post, I have added a data trigger like so:

<DataTrigger Binding="{Binding Attribute1}" Value="{x:Null}">
    <Setter Property="Source" Value="{x:Null}"/>
</DataTrigger>

Also, in order to prevent an index out of bound issue, I have split the item attributes in my view model into three properties (i was initially returning String.Empty, but changed it to null to work with the data trigger):

public string Attribute1
{
    get { return _item.Attributes.Count > 0 ? _item.Attributes[0].Image : null; }
}
public string Attribute2
{
    get { return _item.Attributes.Count > 1 ? _item.Attributes[1].Image : null; }
}
 public string Attribute3
{
    get { return _item.Attributes.Count > 2 ? _item.Attributes[2].Image : null; }
}

So my problem is that I would like to have this data trigger work for all three image attributes and the corresponding image controls (along with some other properties like width, height, margin etc.). So I think, put it in a style and reference it as a static resource. This won't work when I have three different properties with different names (Attribute1, Attribute2, Attribute3). So now I am stuck doing it this way:

<Image>
    <Image.Style>
        <Style TargetType="Image">
            <Setter Property="Source" Value="{Binding Attribute1}" />
            <Setter Property="Width" Value="44" />
            <Setter Property="Height" Value="45" />
            <Setter Property="Margin" Value="5" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Attribute1}" Value="{x:Null}">
                    <Setter Property="Source" Value="{x:Null}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>
</Image>

This is repeated for the other two image controls, except the Attribute1 is substituted with Attribute2 and Attribute3.

So then I was wondering if there is a way to bind to a collection instead, such as

<DataTrigger Binding="{Binding Attributes}" Value="{x:Null}">
    <Setter Property="Source" Value="{x:Null}"/>
</DataTrigger>

... and then specifcy the index I'm interested in the image control binding, outside of the template (i guess like passing a parameter to the data trigger).

Any ideas... is there another approach if this isn't possible?

Community
  • 1
  • 1
Jason Down
  • 21,731
  • 12
  • 83
  • 117
  • 1
    Dude, Why do you overcomplicate it all so much? Just use an `ItemsControl` bound to some `ObservableCollection` and forget `Attribute1, Attribute2, Attribute3...` – Federico Berasategui Jun 12 '13 at 18:26
  • @HighCore: I'm still pretty green in the WPF world, so I don't always pick the easiest or most proper way to do something. – Jason Down Jun 12 '13 at 18:32

1 Answers1

7
<ItemsControl ItemsSource="{Binding Attributes}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Image Source={Binding Something} x:Name=Image/>
            <DataTemplate.Triggers>
                <DataTrigger Binding={Binding Something} Value={x:Null}>
                     <Setter TargetName=Image Property=Source Value={x:Null}/>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Typed this manually in the answer, not real XAML. But you should be able to understand what I mean.

Edit: If your Attributes are just strings, use "{Binding}" wherever I placed "{Binding Something}"

--UPDATE (I'll update your answer with what I did since your answer was correct)--

<ItemsControl ItemsSource="{Binding Attributes}"
              Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Margin="5">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"
                        HorizontalAlignment="Left" VerticalAlignment="Top" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Image x:Name="Image"
                   Source="{Binding}"
                   Width="45" Height="44" Margin="5" />
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding}" Value="{x:Null}">
                    <Setter TargetName="Image" 
                            Property="Source" Value="{x:Null}" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And to limit it to the first three item attributes, this is what I had in my view model's constructor. Attributes is a SmartObservableCollection property (custom ObservableCollection with and AddRange method and a couple other goodies) with an _attributes backing field:

_attributes = new SmartObservableCollection<string>();
var images = from attributes in _item.Attributes
             select attributes.Image;

Attributes.AddRange(images.Take(3));
Jason Down
  • 21,731
  • 12
  • 83
  • 117
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154