Data binding in DataTemplate of ListBox - c#

I have a list box defined as below in xaml:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Visibility="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="tb_Text" Text="{Binding IsSelected}"> </TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
I want to bind some property of tb_Text (say text property) to some property of the datacontext of the listbox (IsSelected property in this example). Is there a way to achieve that?
Note: IsSelected property is coming from the templated parent which has this listbox defined in it's Template.

Using ElementName and binding to the Visibility property of the Ellipse does the trick.

Related

Make element visible when Validation.HasError is true

I have the following xaml markup:
<GroupBox Margin="10" Padding="20" Header="Case 5 - Custom Error Object">
<GroupBox.DataContext>
<local:ViewModel4/>
</GroupBox.DataContext>
<StackPanel>
<Label Name="AdornerElement" Style="{StaticResource ResourceKey=AdornerElementStyle}"/>
<TextBox Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
Margin="0 10"
Validation.ValidationAdornerSite="{Binding ElementName=AdornerElement}">
</TextBox>
</StackPanel>
</GroupBox>
and the following style:
<Style x:Key="AdornerElementStyle" TargetType="Label">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal" Background="LightCoral">
<Image Source="/clipart.png" Width="24" Margin="10"/>
<ItemsControl ItemsSource="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ErrorContent.ValidationMessage}"
Style="{StaticResource CustomErrorTypeStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=AdornerElement, Path=Validation.HasError}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Everything works fine except Trigger.
If I set up the property "Visibility" to "Visible" initially, then I can see that error messages are shown correctly.
If I use style shown before, then the Label remains to be collapsed.
Please, help me to use triggers correctly to achieve the final result.
This is not how Validation.ValidationAdornerSite works. This attached property just defines the element that will be adorned with the validation error template. By default this is the element that is validating, the Binding.Target to be more precise.
When you set Validation.ValidationAdornerSite the binding engine will automatically set the attached Validation.ValidationAdornerSiteFor property to reference the element that Validation.ValidationAdornerSite was originally set on.
This means that Validation.HasError and other related attached properties are always set on the Binding.Target and not on the adorner site.
That's why your triggers don't work: they are triggering on the Label instead of the TextBox (where the validation/binding error is registered).
To fix it, the DataTrigger must get the Validation.HasErrors attached property value of the Validation.ValidationAdornerSiteFor attached property, which references the original Binding.Target (the TextBox).
<Style x:Key="AdornerElementStyle"
TargetType="Label">
<Setter Property="Visibility"
Value="Collapsed" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal"
Background="LightCoral">
<Image Source="/clipart.png"
Width="24"
Margin="10" />
<ItemsControl ItemsSource="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent.ValidationMessage}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.HasError)}"
Value="True">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Remarks
It's not clear why you are using a Label to display the error messages. You could simply define an error template, which is a ControlTemplate that is referenced by the attached Validation.ErrorTemplate property. This would simplify your code significantly.
The following example creates the exact same visual error feedback but without the hassle for an additional Label and associated triggers to manage the visibility of the error messages. All this is all handled by the WPF binding engine.
<Window>
<Window.Resources>
<!-- The Validation.Errors property is the DataContext of this template -->
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel>
<Border BorderBrush="Red"
BorderThickness="1"
Background="LightCoral">
<StackPanel Orientation="Horizontal">
<Image Source="/clipart.png"
Width="24"
Margin="10" />
<ItemsControl ItemsSource="{Binding}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent.ValidationMessage}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<Border BorderBrush="Transparent"
BorderThickness="1"
HorizontalAlignment="Left">
<!-- Placeholder for the Binding.Target -->
<AdornedElementPlaceholder x:Name="AdornedElement" />
</Border>
</StackPanel>
</ControlTemplate>
</Window.Resource>
<StackPanel>
<TextBox Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{StaticResoucre ErrorTemplate}" />
</StackPanel>
</Window>

Conditional ItemsControl.ItemTemplate binding

I'm working on a project and as redundant as it is - I'm trying to do it entirely without code-behind.
I have a User Control called MessagePanel that's meant to wrap messages received through the TCP connection.
Messages can either be text-only or image-only and my control is meant to handle both using different data templates.
Template for texts:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Template for images:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I'm having an issue figuring out how to trigger for either of them to be used based on a IsImage boolean property.
I would appreciate any help.
There are several ways to achieve this, and you would typically use a DataTemplateSelector that is assigned to the ItemsControl's Item​Template​Selector property.
You may however write a XAML-only solution with a DataTrigger in the ItemContainerStyle of the ItemsControl:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsImage}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding Image}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Note that you might probably not need to have an IsImage property. The DataTrigger could as well check the Image property for null:
<DataTrigger Binding="{Binding Image}" Value="{x:Null}">

Combobox WPF template item and selected item

I try to do a template for a combobox. The ItemTemplate is ok but not the selected item as u can see below:
My code:
<ComboBox ItemsSource="{Binding CouleursList}"
SelectedItem="{Binding SelectedCouleur}"
Grid.Column="1" Grid.Row="2">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Stroke="Black" Margin="1" Height="15"
HorizontalAlignment="Stretch">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Path=., Converter={StaticResource ColorConverter}}"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If I gave a fixed width, that works but the rectangle is align to the left and center for the list
Thanks !
The selected item content isn't displayed in a ComboBoxItem, so your HorizontalContentAlignment style setter won't apply.
You can set that property on the ComboBox itself, however:
<ComboBox
ItemsSource="{Binding CouleursList}"
SelectedItem="{Binding SelectedCouleur}"
HorizontalContentAlignment="Stretch"

Need help create wpf listview custom control

I want to create a WPF custom control inherit ListView. I know how to build the listview in a normal WPF window but in custom control's Generic.xaml I don't know how to do it.
Can you help me transform the listview below into custom control's Generic.xaml format please?
Thanks.
<ListView x:Name="listView"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionMode="Single"
ItemContainerStyle="{DynamicResource MyListViewItemContainerStyle}"
ItemsSource="{TemplateBinding ItemsSource}" >
<ListView.Resources>
<DataTemplate x:Key="FirstColumnDataTemplate" >
<Border BorderBrush="#FFABADB3" BorderThickness="1,0,1,1" Margin="-6,0,-6,0">
<Grid Margin="6,2,6,2">
<ContentPresenter ContentTemplate="{Binding FirstColumnItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomControl1}}"
Focusable="False"
RecognizesAccessKey="True"/>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="SecondColumnDataTemplate">
<Border BorderBrush="#FFABADB3" BorderThickness="0,0,1,1" Margin="-6,0,-6,0">
<Grid Margin="6,2,6,2">
<ContentPresenter ContentTemplate="{Binding SecondColumnItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomControl1}}"
Focusable="False"
RecognizesAccessKey="True"/>
</Grid>
</Border>
</DataTemplate>
<Style x:Key="MyListViewItemContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,-4,0,0"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn x:Name="FirstColumn"
Header="{TemplateBinding FirstColumnHeader}"
CellTemplate="{DynamicResource FirstColumnDataTemplate}"/>
<GridViewColumn x:Name="SecondColumn"
Header="{TemplateBinding SecondColumnHeader}"
CellTemplate="{DynamicResource SecondColumnDataTemplate}"/>
</GridView>
</ListView.View>
</ListView>
FirstColumnHeader, SecondColumnHeader are string dependency properties. FirstColumnItemTemplate, SecondColumnItemTemplate are DataTemplate dependency properties of the custom control.
The custom control itself inherit ListView like:
public class CustomControl1 : ListView

DataGridColumnHeader with StackPanel, bind content of TextBlock/Label to ColumnName, dynamic grid

I have a dynamically generated DataGrid bound to a DataTable property in my ViewModel.
I have AutoGenerateColumnHeaders = true, and it's working fine. However, I'm using a DataTemplate to cover the Header with a StackPanel containing a Label and Button. I cannot seem to figure out how to bind the Label Content to the DataGridColumnHeader. I have tried with and without FindAncestor, but I believe the following is the closest to where I need to be...Question is on the Label Content="{}"
<local:UserControlViewBase.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel Width="Auto" Orientation="Horizontal">
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:UserControlViewBase}},Path=DataContext.TestList.ColumnName}" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Button Content="Ok" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:UserControlViewBase.Resources>
//local:UserControlViewBase is just a UserControl with some extra bells and whistles added.
I'm fairly new to WPF and I'm assuming I'm just missing something with the binding - I'm still learning.
Thanks.
This is what I did to get it to work. I had to change the findancestor to look for the DataGridColumnHeader instead of the user control. I then was able to access the Column.Header property:
<local:UserControlViewBase.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel Width="Auto" Orientation="Horizontal">
<Label Width="75" Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridColumnHeader}},Path=Column.Header}" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Button Content="Ok" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:UserControlViewBase.Resources>

Categories

Resources