ListBox ItemTemplate based on parent binding value - c#

Basically, I am looking to create a custom template for my listitems. One template will use checkboxes, while the other will use radioboxes. This is meant to emulate when multiselect is allowed or not. However, I have tried many different ways, with the most promising being the DataTemplateSelector, however I need to create a Dependency Property so that I can pass in the boolean IsMultiSelect value. But, I need a DependencyObject within the Selector, and the closest I can get is the contentpresenter. I know I can get the parent control based off of that, but that seems like a hack. Is there any way to accomplish what I am looking to do?

I'm not completely sure if I understood everything correctly, but this may be helpful:
<ListBox SelectionMode="Multiple">
<!--<ListBox SelectionMode="Single">-->
<ListBox.Items>
<TextBlock Text="Test 1" />
<TextBlock Text="Test 2" />
<TextBlock Text="Test 3" />
<TextBlock Text="Test 4" />
<TextBlock Text="Test 5" />
<TextBlock Text="Test 6" />
</ListBox.Items>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Resources>
<DataTemplate x:Key="SingleSelectionModeItemTemplate">
<RadioButton IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}},
Mode=TwoWay}"
Content="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="MultiSelectionModeItemTemplate">
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}},
Mode=TwoWay}"
Content="{Binding}" />
</DataTemplate>
</Style.Resources>
<Style.Triggers>
<Trigger Property="SelectionMode"
Value="Single">
<Setter Property="ItemTemplate" Value="{StaticResource SingleSelectionModeItemTemplate}" />
</Trigger>
<Trigger Property="SelectionMode"
Value="Multiple">
<Setter Property="ItemTemplate" Value="{StaticResource MultiSelectionModeItemTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

Related

C# WPF Changing ContentControl Content based on DataTrigger not working

I am trying, with no luck, to change the content of a content control based upon a DataTrigger, that checks a binding to see if it matches a type, and if it does change the contentcontrols displayed content.
In short, I want this ContentControl's Content to change if the Source is a certain type. I have attempted to do this using Styles / DataTriggers
<ContentControl>
<ContentControl.Content>
<TextBlock Name="CollectionControlTextBox1"
Tag="."
PreviewMouseDown="CollectionControlDefaultDockPanel_MouseButtonDown"
PreviewMouseUp="CollectionControlTextBox1_PreviewMouseUp"
MouseMove="CollectionControlDefaultDockPanel_MouseMove"
Drop="CollectionControlDefaultDockPanel_Drop"
Foreground="{Binding Meta.ColorBrush, Mode=OneWay, TargetNullValue={StaticResource TextBrush}, FallbackValue={StaticResource TextBrush}}"
AllowDrop="True"
MinHeight="20"
Padding="5 2 0 0"
KeyboardNavigation.IsTabStop="False"
Focusable="True"
TextTrimming="CharacterEllipsis"
ContextMenu="{Binding ParentControl.MemberContextMenu, Mode=OneWay}"
Text="{Binding DisplayName, Mode=OneWay}">
<TextBlock.CommandBindings>
<CommandBinding Command="{x:Static iugo:EditorCommands.Metadata}" Executed="Metadata" />
</TextBlock.CommandBindings>
</TextBlock>
</ContentControl.Content>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Source, Converter={StaticResource IsAssociation}}" Value="True">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding IdEntry.Id}" ToolTip="{Binding IdEntry.Entry.Name}" FontSize="10" Foreground="{StaticResource DisabledTextBrush}" HorizontalAlignment="Center" Margin="0, 3, 0, 0"/>
<TextBlock Text="{Binding IdEntry.Entry.Name}" ToolTip="{Binding IdEntry.Entry.Name}" FontSize="13" Margin="5, 0, 0, 0"/>
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I have tested that the converter: IsAssociation is correctly being hit, and is correctly the right value, but the Content does not change to a stackpanel as defined in the DataTrigger. I have confirmed this by using Visual Studios' Visual Tree, and it still links to the old content.
A directly set Content like
<ContentControl>
<Content>
...
</Content>
</ContentControl>
has higher value precedence than a value set by a Setter in a Style Trigger. Your DataTrigger is hence ignored.
Instead of directly setting the Content, move the initial value to another Setter:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<TextBlock ...>
...
</TextBlock>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger ...>
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
...
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

WPF ToolTip Text Alignment

I have the following ListViewItem:
<GridViewColumn Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}},
Converter={StaticResource MathConverter}, ConverterParameter=(x/10)*1}">
<GridViewColumn.Header>
<GridViewColumnHeader Content=" Total Fees "
HorizontalContentAlignment="Right" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GarnishmentTotals.TotalFees, StringFormat={}{0:c}}"
TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The Style for this item is as follows:
<Style x:Key="MultipleGarnishmentsStyle" TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Garnishments.Count, Converter={StaticResource GreaterThanEqualToBooleanConverter}, ConverterParameter=2}" Value="True"
h:TriggerTracing.TriggerName="MultipleGarnishmentsStyle_Trigger1"
h:TriggerTracing.TraceEnabled="True">
<!-- Leave the content alone and just change the format string -->
<Setter Property="TextBlock.FontWeight" Value="UltraBold" />
<Setter Property="ToolTip" Value="Employee has multiple garnishments. Double click to view details." />
</DataTrigger>
</Style.Triggers>
</Style>
I am having an issue with the alignment of the text within the ToolTip.
The text seems to be using the ListViewItems text alignment (Right). I have tried adding the following properties to teh style but nothing changes:
<Setter Property="TootlTip.HorizontalAlignment" Value="Left" />
<Setter Property="TootlTip.HorizontalContentAlignment" Value="Left" />
<Setter Property="TootlTip.Width" Value="300" />
Is there a way to either increase the ToolTip width so there is no wrapping of the text or make the ToolTip text left aligned.
I got the same problem and after some digging (RibbonToolTip is inheriting alignment from a textbox), I found a solution. It maybe late, but it would benefit others.
What you need to do is to setup you ToolTip style like this:
<Style TargetType="ToolTip">
<Setter Property="TextBlock.TextAlignment" Value="Left"/>
</Style>
Since Tooltip is a ContentControl you can customize its content as you want. Consider the following example:
<Grid>
<Button Content="Hi there!">
<Button.Style>
<Style TargetType="Button">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip MaxWidth="100" HorizontalContentAlignment="Center">
<TextBlock Text="fsdfasdfasdfasdfasdfasdfadsfasdf fadsfadsf adfa fdasfasdfasdfa adfasd" TextAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
You can play around with the content's properties (in this case the TextBlock) in order to achieve the final look you want.

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

How do you bind a viewmodel which implements IDataErrorInfo to a UserControl and propagate the validation errors?

I've written a UserControl that exposes a few dependency properties to modify the layout of the control (i.e. think of it as a generic type-safe editor control - so my data types are dates (edited through a date picker), enumerations (edited through a combobox) and a numeric (edited through a textbox). I've also exposed the value of the 3 editor controls as a dependency property so that it can be databound.
The controls within the usercontrol all bind to the exposed dependency properties to get their values (with appropriate converters where necessary).
This control forms a tiny piece of a larger UI which binds to a viewmodel - the value to be edited, the datatype flag for the custom control and a list of possible valid values are all bound to an object in the viewmodel.
My problem is this: I've implemented IDataErrorInfo on the viewmodel and set the controls' bindings within the custom usercontrol to have ValidatesOnDataErrors=True, NotifyOnValidationError=True but the validation is not displaying.
This is the XAML for my usercontrol (there is no other code-behind logic beyond the dependency property declarations):
<UserControl.Resources>
<!-- Converters -->
<local:ValidationBooleanToImageConverter x:Key="ValidationBooleanToImageConverter"/>
<local:ValidationErrorToStringConverter x:Key="ValidationErrorToStringConverter"/>
<local:DataTypeToVisibilityConverter DataType="Date" x:Key="DateDataTypeToVisibilityConverter"/>
<local:DataTypeToVisibilityConverter DataType="Numeric" x:Key="NumericDataTypeToVisibilityConverter"/>
<local:DataTypeToVisibilityConverter DataType="Enumeration" x:Key="EnumerationDataTypeToVisibilityConverter"/>
<local:DateToStringConverter x:Key="DateToStringConverter"/>
<!-- Styles -->
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors).CurrentItem, Converter={StaticResource ResourceKey=ValidationErrorToStringConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<toolkit:DatePicker Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=DateDataTypeToVisibilityConverter}}" SelectedDate="{Binding Path=Value, ElementName=UserControl, Converter={StaticResource ResourceKey=DateToStringConverter}}"/>
<ComboBox Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=EnumerationDataTypeToVisibilityConverter}}" SelectedItem="{Binding Path=Value, ElementName=UserControl, Mode=TwoWay}" ItemsSource="{Binding Path=Items, ElementName=UserControl}" />
<TextBox x:Name="_txtValue" Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=NumericDataTypeToVisibilityConverter}}" Text="{Binding Path=Value, ElementName=UserControl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
<Image Grid.Column="2" Grid.Row="0" Source="{Binding ElementName=_txtValue, Path=(Validation.HasError), Converter={StaticResource ResourceKey=ValidationBooleanToImageConverter} }" ToolTip="{Binding ElementName=_txtValue, Path=ToolTip}" Width="16" Height="16"/>
</Grid>
...and the usercontrol reference from within the larger View is...
<control:ValueEditorControl DataType="{Binding Path=ContextualSelectedTagDataType}" Items="{Binding Path=ContextualSelectedTagItems}" Value="{Binding Path=ContextualSelectedTagDataObjectValue, Mode=TwoWay}" Height="25" VerticalAlignment="Top"/>
Can anyone point me in the right direction?
Does your VM implement INotifyPropertyChanged? Even if you implement IDataErrorInfo if WPF isn't notified of changes to the VM then it won't bind to those changes.
That being said I would change your ToolTip setter to this:
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
If you want the entire Style I would recommend this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<AdornedElementPlaceholder Name="controlWithError" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="LightPink"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>

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>

Categories

Resources