i have a ListView which contains objects bound from an collection. The representation of the objects I have set with a DataTemplate. Now I want to do the following.
There are two TextBlocks in my DataTemplate:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Path}"></TextBlock>
</StackPanel>
</DataTemplate>
I already have specified an ItemContainerStyle which I use to realize a hover-effect.
<Style TargetType="ListViewItem" x:Key="ContainerStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
... and so on
My aim is to underline the TextBlock which contains the Name, when user moves mouse over the ListViewItem. The Path shouldn't be underlined. How can this be realized? How can an element in DataTemplate can be accessed for each ListViewItem?
Greetings,
Martin
You can do this either by specifying the ControlTemplate for the ListViewItem, or by changing the DataTemplate. The example below shows both methods. Note that you lose the blue background for the selected ListViewItem when you use the ControlTemplate (when you comment it out it returns)
EDIT:
I did not read your requirement well. You only want to underline the Name. Then the only possibility is to use the Datatemplate, or to rewrite the ControlTemplate of the TextBox.
<Window x:Class="Underlining.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<StackPanel>
<ListView ItemsSource="{Binding}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="UnderlineInControlTemplate" BorderThickness="2,0,2,0"
BorderBrush="Transparent">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="UnderlineInControlTemplate"
Property="BorderBrush"
Value="BlueViolet"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Name="UnderlineInDataTemplate" BorderThickness="0,0,0,2"
BorderBrush="Transparent">
<TextBlock Text="{Binding Name}"/>
</Border>
<TextBlock Text="{Binding Path}"/>
</StackPanel>
<DataTemplate.Triggers>
<Trigger Property="TextBlock.IsMouseOver" Value="True">
<Setter TargetName="UnderlineInDataTemplate"
Property="BorderBrush"
Value="Red"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
Related
So I have the following ListBox defined:
<ListBox Grid.Row="1" Grid.Column="0" x:Name="RawLBControl"
ItemsSource="{Binding ProductionLists.Raw}"
Background="LightGray" BorderThickness="2" BorderBrush="Black">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=OneWay}" Background="LightGray"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
There are going to be a number of these, where the only things that changes is the placement (Grid location) and the ItemsSource bindings. Everything else is going to be the same. Question is how do I setup a template so all the ListBoxes use it.
You can define style in application resources and applied it to ListBox later in your code.
<Application x:Class="Q52018469.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style x:Key="MyListBoxStyle" TargetType="ListBox">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Mode=OneWay}" Background="LightGray"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
Using of this style:
...
<Grid>
...
<ListBox Grid.Row="1" Grid.Column="0" x:Name="RawLBControl"
ItemsSource="{Binding ProductionLists.Raw}"
Style="{StaticResource MyListBoxStyle}" />
</Grid>
...
I'm having problems with how validation errors are displayed for TextBox elements in vertical StackPanel. I'm trying to display error messages below TextBox.
I have this error template:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
If I have enough white space below the TextBox, error is displayed fine, but in StackPanel (for example), it does not add extra margin or padding for error messages when there are some, because of adorner layer.
It is expected to be so, according to this source:
Note that the Validation.ErrorTemplate will be displayed on the adorner layer. Elements in the adorner layer are rendered on top of the rest of the visual elements and they will not be considered when the layout system is measuring and arranging the controls on the adorned element layer.
How can I display validation error messages, so that they won't show over other elements in StackPanel?
You can also consider to include your error template in the TextBox's template.
Something like that (of course it can be improved):
<Style x:Key="eTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<StackPanel>
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="1" Padding="2">
<ScrollViewer Name="PART_ContentHost" Focusable="False"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
Background="#00FFFFFF" />
</Border>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(Validation.Errors)}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In this way the ItemsControl is considered for the layout computation.
Ok, I found the solution with converter. I ended up with style similar to this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="10" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="0,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="Margin" Value="{Binding (Validation.Errors).Count, RelativeSource={RelativeSource Self}, Converter={StaticResource ErrorsToMarginConverter}}"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
and converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
var errors = (int)value;
return new Thickness(10, 10, 10, (errors * 20));
}
return value;
}
Following my first question, I'm left with the following XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border BorderBrush="Transparent" Background="Transparent">
<ContentPresenter x:Name="TabItemContent" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="TextElement.Foreground" Value="DarkOrange" TargetName="TabItemContent"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First"/>
<TextBlock Text="Tab"/>
</StackPanel>
</TabItem.Header>
<TextBlock Text="TextBlock"/>
</TabItem>
<TabItem Header="TAB2">
<TextBlock Text="TextBlock too"/>
</TabItem>
</TabControl>
</Grid>
</Window>
The styling works well for the second tab (which has a simple Header), but doesn't for the first one (with a 'complex' header).
Is there a way to bypass this behavior? I can't think of one myself; I tried replacing the "TextElement" by "TabItem" with no success.
EDIT: removed irrelevant XAML parts.
Yes, this is a difficult problem; inheritance works by the logical tree, and the logical parent of the complex header is the TabItem. If you set the Foreground on a TabItem, the content of the tabitem will inherit that foreground as well, and that is something that you don't want.
Luckily there is a simple solution: put the UI in a HeaderTemplate instead of the Header property. The root of the template doesn't have a logical parent, but it does have the ContentPresenter as its TemplatedParent, and apparently inheritance also propagates from templated parent to the root in the template.
So I came up with a somewhat elegant solution, even though extremely limited (it will only be usable if your custom headers only are meant to contain 2 text elements, if you want more, this won't work, although it doesn't seem impossible to replace a TextBlock by a Image; but yeah, 2 elements).
I just added a HeaderTemplate setter in the TabItem's Style, and do my dark bidings here, binding to Tag to get the name of the tab.
Here's how it goes:
<Style TargetType="{x:Type TabItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType=TabItem}}" Margin="2,0"/>
<TextBlock Text="{Binding}" Margin="2,0"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<ContentPresenter x:Name="TabItemContent" ContentSource="Header" HorizontalAlignment="Center" Margin="4" RecognizesAccessKey="True"/>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="TabItemContent" Property="TextElement.Foreground" Value="DarkOrange"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And how it's used (placing the above Style in your Window/UserControl/TabControl's resources):
<TabItem Header="{Binding SomeValue}" Tag="TabName">
<some:Controls/>
</TabItem>
Due to the use of a StackPanel, if SomeValue is null, your TabItem will only show "TabName".
I copied some resource about custom button using Content control. And I changed something to be <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=Content}"> For the dataTempalte
<DataTemplate x:Key="PriceDataTemplate" DataType="m:ClickTradeViewModel">
<Button Command="{Binding ExecuteCommand}" Cursor="Hand">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="DarkGray" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#FF345C8B" />
</Trigger>
<DataTrigger Binding="{Binding IsExecuting}" Value="True">
<Setter Property="Background" Value="DimGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<UserControl>
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=Content}"></TextBlock>
</ControlTemplate>
</UserControl.Template>
</UserControl>
</Button>
</DataTemplate>
And for the actual Button, it used
<ContentControl x:Name="AskContentControl" Grid.Column="2"
Margin="5,0,0,0"
Content="{Binding QQ.Bid}"
ContentTemplate="{StaticResource PriceDataTemplate}"/>
I expect the Content will use double Bid's tostring method to render the content, but it shows nothing inside (gray color). In the plot the left side shows the price did exists.
Update: I am not sure what's going on, but with some change <TextBlock Text="{Binding QQ.Ask}"></TextBlock> and set
<ContentControl x:Name="AskContentControl" Grid.Column="2"
Margin="5,0,0,0"
Content="{Binding}"
ContentTemplate="{StaticResource PriceDataTemplate}"/> makes it work.
The problem is then I had to explicitly set the PriceDataTemplate several times for different properties.
It does not work because you are using a Binding with RelativeSource finding a ContentControl but UserControl is also a ContentControl, so what it found is actually the UserControl, not the root ContentControl you thought. In this case you can specify some AncestorLevel as 2 (to find the second ContentControl):
<TextBlock Text="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContentControl, AncestorLevel=2},
Path=Content}"></TextBlock>
However it's not really safe and in this case the implicit DataContext is actually the Content you set for your ContentControl (this DataContext flows down from the DataTemplate through the UserControl's Template). So the Binding can be just simple like this:
<TextBlock Text="{Binding}"></TextBlock>
Note I supposed you keep setting the ContentControl's Content to {Binding QQ.Bid}.
This is a full working solution... I'm late but perhaps it could help others?
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ParametricStudyAnalysis.ScopeSelection.Special"
xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid" x:Class="ParametricStudyAnalysis.ScopeSelection.Special.UserControlAddSpecialSignal"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:UserControlAddSpecialSignalModel></local:UserControlAddSpecialSignalModel>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:UserControlSpecialSignalTtrModel}">
<local:UserControlSpecialSignalTtr/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<GroupBox Header="Signal type" Grid.Row="0" Padding="5">
<xcdg:DataGridControl Name="DataGrid" SelectionMode="Single" ItemsSource="{Binding SpecialSignalEntries}"
SelectedItem="{Binding SpecialSignalEntrySelected}" Height="200">
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Name" Title="Type of special signal" ReadOnly="True"></xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</GroupBox>
<GroupBox Header="Parameters" Grid.Row="1" Margin="0,3,0,0" Padding="5">
<ContentControl Name="MyContentControl"
DataContext="{Binding SpecialSignalEntrySelected, Mode=OneWay}"
Content="{Binding SignalProviderSpecial}">
</ContentControl>
</GroupBox>
</Grid>
</UserControl>
I have a ListView control with a list of system messages in it, and a corresponding "action" button that will help my user resolve the message:
If the message is an error message, I want the Foreground of the text and the action button (the [more...] part) to turn red. As you can see, it's not doing that.
I'm using a Style.Trigger bound to the message data's Severity to set the Foreground. This works fine for the TextBlock in the DataTemplate, but fails to affect the Button in that Template. My XAML looks like this:
<ListView x:Name="ImageCenterStatus" Background="{DynamicResource SC.ControlBrush}" Margin="10,22,10,0" FontSize="12" Grid.Column="1" ClipToBounds="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock TextWrapping="WrapWithOverflow" cal:Message.Attach="[Event MouseUp] = [Action DoStatusAction($dataContext)]" Text="{Binding Path=DisplayedMessage}"/>
<Button x:Name="ActionButton" Content="{Binding Path=DisplayedActionLabel}" FontSize="12" cal:Message.Attach="DoStatusAction($dataContext)" Style="{DynamicResource SC.ActionButtonHack}"></Button>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Severity}" Value="Warning">
<Setter Property="Foreground" Value="#FFCE7A16" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Severity}" Value="Error">
<Setter Property="Foreground" Value="#FFD13636" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The Button has its own Style, SC.ActionButtonHack, but I am very careful not to override the Foreground anywhere that style:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why does the TextBlock in the DataTemplate respond to the Foreground change in the Trigger, but not the Button?
What do I need to do to get the button to respect the Foreground change?
Bind TextElement.Foreground of ContentPresenter to ListViewItem's foreground to get it work:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter"
TextElement.Foreground="{Binding Foreground,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OR
Either bind it on Button itself:
<Button x:Name="ActionButton"
Foreground="{Binding Foreground, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ListViewItem}}"
Content="{Binding Path=Name}" FontSize="12"
Style="{DynamicResource SC.ActionButtonHack}"/>