Setting ItemTemplate based on CheckBox value - c#

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>

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

how to trigger visibility change to child controls with a trigger

I have a databound listbox thet generates items in a datatemplate of type wrappanel with other controls within it. I would like to have the behavior to, when I change the visibility to affect differently the controls within the wrappanel
<WrapPanel Orientation="Horizontal" Tag="{Binding .}" HorizontalAlignment="Stretch" Visibility="{Binding editMode, Converter={StaticResource VisibilityConverter}}">
<Label Width="150" Content="{Binding Path=avaiableAttribute.Text}" Name="lblName"/>
<Label Width="150"
Content="{Binding Path=informationItem.ItemString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Initialized="Label_Initialized"
Name="lblText" />
<ContentPresenter MinWidth="200" MaxHeight="200" Content="{Binding ., Converter={StaticResource InformationItemConverter}, Mode=TwoWay}" HorizontalAlignment="Stretch"
Name="cpValue"
Initialized="ContentPresenter_Initialized"/>
<WrapPanel.Style>
<Style TargetType="{x:Type WrapPanel}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible" >
<Trigger.Setters>
<Setter TargetName="lblText" Property="Visibility" Value="Collapsed" />
<Setter TargetName="cpValue" Property="Visibility" Value="Visible" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</WrapPanel.Style>
</WrapPanel>
I just get the following build error
the property 'targetname' does not represent a valid target for the 'setter' because an element
When using a Setter, the TargetName only applies to elements in a Template. This means your Trigger has to exist in a DataTemplate or ControlTemplate. The easiest way to do what you want to do is to create your own IValueConverter for the inverse of BooleanToVisibilityConverter (ie return Visibility.Collapsed when value is true).

Custom ListBox in WPF

I am trying to create a custom ListBox control in WPF for a chat Messenger. I am using an ellipse to show the online/offline user. The ellipse is to be displayed on left and some text in center of the ListBoxItem.
I want to set the ellipse fill propert to red/green based on some variable.
This is what I have done :
<ListBox Name="myList" HorizontalAlignment="Left" Height="232" Margin="117,74,0,0" VerticalAlignment="Top" Width="207">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Ellipse Name="ellipse" Fill="Red" DockPanel.Dock="Left">
<Ellipse.Triggers>
<Trigger Property="{Binding Online}" Value="True">
<Setter TargetName="ellipse" Property="Ellipse.Fill" Value="Green"/>
</Trigger>
</Ellipse.Triggers>
</Ellipse>
<TextBlock Text="{Binding text}"></TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in the code :
myList.Items.Add(new { text="Hello",Online="True" });
I am getting an error as
Cannot find the static member 'FillProperty' on the type 'ContentPresenter'.
What am I doing wrong here?
Obviously this is wrong: Property="{Binding Online}"
Also you should use a Style for triggers, no need to set TargetName, and you need to take precedence into consideration, and use a Setter for the default value.
you are actually misleading WPF with some of these concerns.
Binding A property on trigger will not work. you have to use DataTrigger insteed of Triggers.
Implementing Trigger on the Fly for any control. most of the times not work. So go with Styles.
While you are creating Ellipse in template make sure you have created enough size for it. So that can be visible to users.
try this.
<Window.Resources>
<Style x:Key="elstyle" TargetType="Ellipse">
<Setter Property="Height" Value="5"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Online}" Value="true">
<Setter Property="Fill" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox x:Name="myList" HorizontalAlignment="Left" Height="232" Margin="117,74,0,0" VerticalAlignment="Top" Width="207">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Ellipse Name="ellipse" Margin="5" DockPanel.Dock="Left" Style="{DynamicResource elstyle}">
</Ellipse>
<TextBlock Text="{Binding Name}"></TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
code behind .
public MainWindow()
{
Random r = new Random();
InitializeComponent();
for (int i = 0; i < 10; i++)
{
myList.Items.Add(new { Name = "Name" + i.ToString(), Online = Convert.ToBoolean(r.Next(-1, 1)) });
}
}
You need to set the initial colour via a Setter and not in XAML. For more information and see this question: Change the color of ellipse when mouse over
there are a few issues in your XAML
set the size of your Ellipse
you need to use Style instead of Ellipse.Triggers
set your Fill color in your Style if you wane be able to change it in XAML by some condition
here an working example for your problem
<DataTemplate>
<!--<DockPanel> juste because i like StackPanel-->
<StackPanel Orientation="Horizontal">
<!--<Ellipse Name="ellipse" Fill="Red" DockPanel.Dock="Left">-->
<Ellipse Name="ellipse" Width="15" Height="15">
<!--<Ellipse.Triggers>-->
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<!--<Trigger Property="{Binding Online}" Value="True">-->
<DataTrigger Binding="{Binding Online}" Value="True">
<Setter Property="Fill" Value="LimeGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="{Binding text}"/>
</StackPanel>
</DataTemplate>

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.

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