Creating template for indexed property - c#

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?

<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));

Related

XAML/C# Hiding a TextBlock if the bound value is empty

in my WPF application I have a listview that only appears if a bound item has an values, this works like so
<ListView Grid.Row="1" Grid.Column="1" Margin="0,5,0,20" BorderThickness="0"
ItemContainerStyle="{StaticResource SelectionlessListViewItemStyle}"
ItemsSource="{Binding MissingAssets}">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
So this will only display the listview if MissingAssets has any values and works fine, above that I wanted a textblock as a header to just say "The following assets could not be found", and I want to hide this text of course if this listview is hidden too, I tried implementing it like this
<TextBlock Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" FontWeight="Bold" Text="The following assets could not be found">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding MissingAssets}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
But for some reason it won't hide even if MissingAssets is empty, I've tried using several different things in Value="" but nothing gets it to work. Is there a property or something I'm forgetting to set?
Thanks
You have many options here. The simplest should be to bind the TextBlock.Visibility to the ListView.Visibilty:
<StackPanel>
<TextBlock Visibility="{Binding Elementname="MissingAssetsListView", Path="Visbibility" />
<ListView x:Name="MissingAssetsListView" />
</StackPanel>
Found a workaround, I created a new string property in my C# code that remains blank unless missing assents are found, if they are found I populate the string and bind that string to a label in my XAML, so if the string is empty then there will be no visible label on the UI

wpf image not being shown in some ListBoxItems

I Have a ListBox with an ItemTemplate defined as follows. My problem is the image is shown in only one item per type. i.e:
How do I make all the items show their relevant status?
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ContentControl x:Name="status">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="NotDownloaded">
<Setter Property="Content">
<Setter.Value>
<Image Source="/Images/help-file24.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Downloaded">
<Setter Property="Content">
<Setter.Value>
<Image Source="/Images/file-complete24.png""/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Error">
<Setter Property="Content">
<Setter.Value>
<Image Source="/Images/file-warning24.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Text="{Binding Url}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
i found this on stackoverflow
If you will use the image in multiple places, then it's worth loading
the image data only once into memory and then sharing it between all
Image elements.
To do this, create a BitmapSource as a resource somewhere:
<BitmapImage x:Key="MyImageSource" UriSource="../Media/Image.png" />
Then, in your code, use something like:
<Image Source="{StaticResource MyImageSource}" />
In my case, I found
that I had to set the Image.png file to have a build action of
Resource rather than just Content. This causes the image to be carried
within your compiled assembly.
Image is an UIElement, meaning it's part of the hierarchy of controls and can only have one parent at any given time.
What your template is doing is set the same Image control instance as the content of many ContentControl. So it only works for the very first ContentControl, and then fails as Image has already a parent.
You may try to replace your ContentControl with an Image and setting Image's source in your DataTriggers.

WPF DataTemplate Binding depending on the type of a property

I have a collection of objects bound to a hierarchical data template, each of my objects have a property on them (lets call it Property "A") that is of a certain type. This type varies among each of the objects.
If the data template contains an image and some text, what would be the best way to change the image that is displayed in the template based on the type of property "A".
I know I could just stick this into a converter and do the binding translation manually in code, but with all the binding facilities available in WPF, I think theres probably a better way.
It's pretty simple to do this within your data template, if you create local data templates and use a ContentPresenter. This template presents objects of type MyObject, displaying an image whose source is determined by the type of the A property next to a TextBlock that displays the content of the Text property:
<DataTemplate DataType="{x:Type MyObject}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type Thing1}">
<Image Source="thing1.png"/>
</DataTemplate>
<DataTemplate DataType="{x:Type Thing2}">
<Image Source="thing2.png"/>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding A}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
If you want to use styles to do this instead, you're going to run into a problem, because data triggers want to look at property values, and the type of the A property is not, itself, exposed as a property.
Unless, of course, you implement one:
public Type AType { get { return A.GetType(); } }
(You'll also need to raise PropertyChanged for AType when the value of A changes.) Once you've done this, you should be able to implement a data trigger in a style, e.g.:
<Style TargetType="Image">
<Setter Property="Source" Value="default.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing1}">
<Setter Property="Source" Value="thing1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing2}">
<Setter Property="Source" Value="thing2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
I think You can do that with triggers.
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding TheProperty}" Value="TheValue">
<Setter Property="Source" Value="NewPath"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
DataTemplateSelector doesn't seem to be a good choice here since you have the same template for all values of A.
Use DataTriggers:
<DataTemplate>
<StackPanel>
<Image x:Name="image" />
<TextBlock>Your text</TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck1">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image1.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck2">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image2.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Haven't tested it, but the idea is like that.

Setting ItemTemplate based on CheckBox value

I have a DataTemplate which contains a CheckBox and ListBox. When the CheckBox is checked, I want to change the ItemTemplate property on the ListBox to change the appearance of each item.
Right now, it looks like this:
<DataTemplate DataType={x:Type MyViewModel}>
<DockPanel>
<CheckBox DockPanel.Dock="Bottom"
Content="Show Details"
HorizontalAlignment="Right"
IsChecked="{Binding ShowDetails}"
Margin="0 5 10 5" />
<ListBox ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource SimpleItemTemplate}"
Margin="10 0 10 5">
<ListBox.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</ListBox.Triggers>
</ListBox>
</DockPanel>
</DataTemplate>
However, when I try to compile, I get the following error messages:
Value 'ItemTemplate' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.
and
Cannot find the static member 'ItemTemplateProperty' on the type 'ContentPresenter'.
I'm still fairly new to WPF, so perhaps there is something I'm not quite understanding?
You need to do this through the ListBox Style rather than directly through its Triggers collection. A FrameworkElement's Triggers collection can only contain EventTriggers (so I'm surprised your sample got as far as complaining about the properties!). Here's what you need to do:
<ListBox ItemsSource="{Binding Items}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemTemplate" Value="{StaticResource SimpleItemTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

Relative binding in style of control inside ListBoxItem template

My problem is with the following code, with binding the IsAvailable property of the MyListBoxItem class. My current solution:
<ListBox ItemTemplate="{StaticResource myTemplate}">
<ListBox.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type local:MyListBoxItem}">
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
... (more datatemplates)
</ListBox.Resources>
</ListBox>
My question: In my solution the value of IsAvailable "goes through" two bindings. The first one binds the value to the Tag property of the Label and then in the style triggers, a trigger checks its value and sets a property of the Label. When I used Binding="{Binding IsAvailable, RelativeSource={RelativeSource AncestorType={x:Type local:MyListBoxItem}}}" it didn't work, because the Style can't see any ancestor of the Label (or something similar reason), it resulted binding errors (with code 4 or 40 maybe), for each item added to the ListBox.
So finally: can I make the solution more simple, or there is no another (better) one?
An important thing I've forgot to mention, sorry: I put the DataTemplate in the ListBox's resources because I have more templates (they are basically differ, so I can't style them with triggers), which I have to switch between sometimes...
The ItemTemplate will take the type that the ItemsSource is bound to. Therefore you should be able to simply bind to IsAvailable, as the ListBox's item type is MyListBoxItem. Try this:
<ListBox ItemsSource="...">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll need to set your ItemsSource property to a Binding to the MyListBoxItem collection.

Categories

Resources