I have a assembly where I define several customs controls (based on existing control) with their own style.
For all of them I define the static constructor where I set DefaultStyleKeyProperty and add the style XAML file to Themes/Generic.xaml.
It's working fine for all of them except for my custom ListView.. It's driving me crazy !
Here is a short sample :
public class EmListView : ListView
{
/// <summary>
/// Constructor
/// </summary>
static EmListView()
{
// Set the default style type
DefaultStyleKeyProperty.OverrideMetadata(typeof(EmListView), new FrameworkPropertyMetadata(typeof(EmListView)));
}
}
XAML file declared in Generic.xaml :
<Style TargetType="{x:Type ui:EmListView}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ui:EmListView}">
<Border Name="Border" Style="{DynamicResource EMLV_ListViewBorderStyle}" Margin="50">
<ScrollViewer Style="{DynamicResource {x:Static GridView.GridViewScrollViewerStyleKey}}">
<ItemsPresenter />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.3" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I don't know why but the only way to get my style applied on my EmListView is to force the style when using it, like :
<ui:EmListView Margin="5" Style="{DynamicResource {x:Type ui:EmListView}}">
I have EmWindow, EmButton, .. All are applying the style automatically except that ListView. Is there something special with ListView ?
Thank you.
EDIT :
I spotted something, it seem that is the declaration of my EmListView that cause the trouble.
Here is a sample declaration inside a window :
<ui:EmListView Margin="5">
<ui:EmListView.View>
<GridView>
<ui:EmSortableGridViewColumn Width="100" DisplayMemberBinding="{Binding artCode, Mode=OneWay}" SortPropertyName="artCode" Header="Code" IsDefaultSortColumn="True" />
<ui:EmSortableGridViewColumn Width="100" DisplayMemberBinding="{Binding artCode, Mode=OneWay}" SortPropertyName="artCode" Header="Code" IsDefaultSortColumn="True" />
<ui:EmSortableGridViewColumn Width="100" DisplayMemberBinding="{Binding artCode, Mode=OneWay}" SortPropertyName="artCode" Header="Code" IsDefaultSortColumn="True" />
<ui:EmSortableGridViewColumn Width="100" DisplayMemberBinding="{Binding artCode, Mode=OneWay}" SortPropertyName="artCode" Header="Code" IsDefaultSortColumn="True" />
</GridView>
</ui:EmListView.View>
</ui:EmListView>
The style doesn't apply and it constantly throw me a warning saying that it cannot define OverridesDefaultStyle property in the style.
If I simply declare it like this :
<ui:EmListView Margin="5" />
I have no warning and my style is set correctly.
I solved my issue, I did a crappy workaround in my EmListView constructor as follow :
public EmListView()
{
Style defaultStyle = (Style)Application.Current.TryFindResource(typeof(EmListView));
if (defaultStyle != null)
{
this.OverridesDefaultStyle = true;
this.Style = defaultStyle;
}
}
If someone can tell me why I have to do this trick only for my custom ListView control I will be happy to know.
Related
I made a custom combobox where I have a TextBlock (named mySelectedContent) to display the selected item and a TextBox for editing in "IsEditable" mode. I have a MultiDataTrigger that is being shot correctly, however, I am unable to "catch" the text of the selected item and put it into the TextBlock. How should be mounted the correct expression in place of "???". Thanks a lot!
Here is the code of the trigger (I'm showing mainly the part of the trigger because it's just in it the problem):
<ComboBox.Resources>
<Style x:Key="myComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<ToggleButton>
...
</ToggleButton>
<TextBlock
Name="mySelectedContent"
.../>
<TextBox x:Name="myEditableTextBox"
.../>
<Popup>
...
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
...
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="myEditableTextBox" Property="Visibility" Value="Hidden"/>
<Setter TargetName="mySelectedContent" Property="Visibility" Value="Visible"/>
<Setter TargetName="mySelectedContent" Property="Text" Value="???"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Resources>
It was solved with cYounes first suggestion. I used:
Value={Binding ElementName=MyEditableTextBox Path=Text}
and it works as expected!
Thanks!
That's way too much work when you simply could have used the Tag property to get the value easily with 2 lines:
in XAML:
<ComboBoxItem Content="This Value" Tag="This Value"/>
Then:
GetValue=ComboBoxName.SelectedItem.Tag.ToString()
will give you "This Value" and not
"System.Windows.Controls.ComboBoxItem: This Value"
Much simpler, faster and less time consuming.
I have a Button Style with a Template containing a ContentPresenter, in which I am attempting to bind the Fill of a Path to the Foreground of a button:
<!-- This is inside the template of a button style -->
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
I also have a Path with no Fill set, that I can reference in the button as the content, like so:
<Button Style="{DynamicResource MyButtonStyle}" Content="{DynamicResource PathIcon}" Foreground="Blue"/>
I would expect the Path inside the button to be blue, but it isn't... it doesn't grab the foreground from the button.
How can I get the Path to bind to the color of the button?
Thank you!
P.S.:
If I put a hardcoded color in the Value (i.e. Value="Red"), the Path inside the button is red... so I know that works...
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="Red"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
Edit:
Here is the complete Style and ControlTemplate:
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource White_Brush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid" Background="Transparent">
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Should affect Text as well as Paths in the Content property of the button! -->
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Okay, let's order:
it doesn't grab the foreground from the button.
In styles this construction:
RelativeSource={RelativeSource AncestorType=Button}
will not work, because the Style is just the collection of setters, he does not know about control, are there, specifically about the content of the visual tree. Because RelativeSource should refer to the items above in the visual tree. For this purpose, usually using DataTemplate or ControlTemplate.
If I put a hardcoded color in the Value (i.e. Value="Red")
Yes, in this case, will be working, and always better to create the design of the form:
<SolidColorBrush x:Key="MyButtonColor" Color="Blue" />
And use it for control, like Button:
<Button Background="{StaticResource MyButtonColor}" ... />
and in Style or elsewhere:
<Setter Property="Fill" Value="{StaticResource MyButtonColor}" />
That is, it is better not to depend on the element parameters (background color, etc.) located in a visual tree, because it can:
May move to another panel (Grid, StackPanel) or UserControl
May leave from the project
And brushes in the as resources will always be in one place, changing them in this place, all the elements of their pick up. Also colors can be stored in a special data model that does not depend on the specific technical implementations (resources, variables) in which the data can come from an external source, such as the project/config settings.
If possible, it is better to avoid the use of dynamic resources due to unnecessary use of system perfomance (and in some cases memory leaks), in your cases they are not needed.
Dynamic resources are usually explicitly defined for SolidColorBrush and another species brushes, because by default they are frozen, and they not recommended changed because of the above mentioned reasons (memory leaks). More information can be found here:
Freezable Objects Overview on MSDN
Edit
As I understand it, you want to make universal Style for Button to make the contents of Path or Text (in the case of simultaneous use will be easier). As I have already mentioned above, RelativeSource should be around ControlTemplate, therefore, the Path will be in the Grid with the ContentPresenter.
To style knew, which is provided for the text or for the path, to the Tag (optional property) indicates two properties: OnlyText or OnlyPath.
To set the data for the Path, I've created a attached dependency property, and prescribed it in the ControlTemplate.
Below is a complete example:
XAML
<Window x:Class="ButtonPathHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ButtonPathHelp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="Green_Brush" Color="Green" />
<SolidColorBrush x:Key="Black_Brush" Color="Black" />
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource Green_Brush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid">
<ContentPresenter x:Name="MyContent"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" />
<Path x:Name="MyPath"
SnapsToDevicePixels="True"
Width="20"
Height="18"
Stretch="Fill"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MyDependencyClass.DataForPath)}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
<Trigger Property="Tag" Value="OnlyText">
<Setter TargetName="MyPath" Property="Visibility" Value="Collapsed" />
<Setter TargetName="MyContent" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Tag" Value="OnlyPath">
<Setter TargetName="MyPath" Property="Visibility" Value="Visible" />
<Setter TargetName="MyContent" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<WrapPanel>
<WrapPanel.Resources>
<sys:String x:Key="Save">
F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,
55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,
55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z
</sys:String>
<sys:String x:Key="Search">
F1 M 23.4454,49.2637L 31.7739,41.1598C 30.6986,39.2983 30.4792,37.1377 30.4792,34.8333C 30.4792,27.8377 35.7544,
22.1667 42.75,22.1667C 49.7456,22.1667 55.4167,27.8377 55.4167,34.8333C 55.4167,41.8289 49.7456,47.1042 42.75,
47.1042C 40.5639,47.1042 38.5072,46.9462 36.7125,45.9713L 28.3196,54.1379C 27.0829,55.3746 24.6821,55.3746 23.4454,
54.1379C 22.2088,52.9013 22.2088,50.5004 23.4454,49.2637 Z M 42.75,26.9167C 38.3777,26.9167 34.8333,30.4611 34.8333,
34.8333C 34.8333,39.2056 38.3777,42.75 42.75,42.75C 47.1222,42.75 50.6667,39.2056 50.6667,34.8333C 50.6667,
30.4611 47.1222,26.9167 42.75,26.9167 Z
</sys:String>
</WrapPanel.Resources>
<Button Name="SaveButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Save}"
Margin="10" />
<Button Name="JustText"
Style="{StaticResource Button_Style}"
Tag="OnlyText"
Content="Just Text"
Margin="10" />
<Button Name="SearchButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Search}"
Margin="10" />
</WrapPanel>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyDependencyClass : DependencyObject
{
#region IsCheckedOnDataProperty
public static readonly DependencyProperty DataForPathProperty;
public static void SetDataForPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(DataForPathProperty, value);
}
public static string GetDataForPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(DataForPathProperty);
}
#endregion
static MyDependencyClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(String.Empty);
DataForPathProperty = DependencyProperty.RegisterAttached("DataForPath",
typeof(string),
typeof(MyDependencyClass),
MyPropertyMetadata);
}
}
Note: In the Style I have not used TemplateBinding for attached property, because TemplateBinding doesn’t work outside a template or outside its VisualTree property, so you can’t even use TemplateBinding inside a template’s trigger. Therefore, we must use the construction {RelativeSource TemplatedParent} and a Path equal to the dependency property whose value you want to retrieve.
Output
To download the entire example please follow this link.
I stumbled across simillar problem but was wondering how to get to the 'Foreground Colour' of the Button in its DISABLED state (to have correct colour of my drawing). Here is a finally simple sollution. No templates, No styles, no code, nothing at all. Just the right relative binding :-) :
<StackPanel Orientation="Horizontal">
<Button Height="22" IsEnabled="False">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="90"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
<Button Height="22" IsEnabled="True">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="180"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
</StackPanel>
I would like to take the xaml I currently have for a ComboBox (below), and condense it into something like the Style also shown below. I think this should work, but I have a 'Type'ing issue and not sure how to resolve it
"Cannot resolve the Style Property 'Margin'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property.)
As I look at the existing ComboBoxStyle (also below) that I'm looking to base this new style off of, I see that I hadn't used x:Type, but it does seem to work.
Is there any reason this new style shouldn't work? What must I change?
Cheers,
Berryl
combo box, as is, working):
<ComboBox
x:Name="cboDepartmentFilter" Style="{StaticResource ComboBoxStyle}"
Margin="{StaticResource FliterPanelItem_Margin}" Width="{StaticResource FilterPanelItem_Width}"
ItemsSource="{Binding Path=DepartmentFilterControl.Choices}"
ToolTip="{Binding DepartmentFilterControlData.ToolTipTitle}"
/>
what I want:
<ComboBox Style="{StaticResource FilterPanelComboBoxStyle}" DataContext="{Binding DepartmentFilterControl}" />
<!- in some resource file ->
<Style x:Key="FilterPanelComboBoxStyle" BasedOn="{StaticResource ComboBoxStyle}">
<Setter Property="Margin" Value="{StaticResource FliterPanelItem_Margin}" />
<Setter Property="Width" Value="{StaticResource FilterPanelItem_Width}" />
<Setter Property="ItemsSource" Value="{Binding Choices}" />
<Setter Property="ToolTip" Value="{Binding ToolTipTitle}" />
</Style>
<!--
This style defines a common margin for items in a filter panel.
-->
150
existing ComboBoxStyle:
<!-- ComboBox Style -->
<Style x:Key="ComboBoxStyle" TargetType="ComboBox">
<Setter Property="Background" Value="{StaticResource headerBrush}" />
...
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle" Value="{StaticResource ComboBoxItemStyle}" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
You still need to specify the TargetType in the derived style. (Or you prefix the properties with "ComboBox.")
I'm working on dragging objects around a Canvas, which are encapsulated in ListBoxItems -- the effect being to create a simple pseudo desktop.
I have a ListBox with a Canvas as the ItemsPanelTempalte, so that the ListBoxItems can appear anywhere on screen:
<ListBox ItemsSource="{Binding Windows}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I have a Style to define how the ListBoxItems should appear:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<local:PseudoWindowContainer Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The "PseudoWindowContainer" extends from the ContentControl and has its own Style applied to make it look like a dialog box (title bar, close button, etc...). Here is a chunk of it:
<Style TargetType="{x:Type local:PseudoWindowContainer}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Width" Value="{Binding Width, Mode=TwoWay}" />
<Setter Property="Height" Value="{Binding Height, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PseudoWindowContainer}">
<Grid Name="LayoutRoot" Background="White">
<!-- ... snip ... -->
<Border Name="PART_TitleBar" Grid.Row="0" Background="LightGray" CornerRadius="2,2,0,0" VerticalAlignment="Stretch" Cursor="Hand" />
<TextBlock Name="TitleBar_Caption" Text="{Binding DisplayName}" Grid.Row="0" Background="Transparent" Padding="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<Button Name="TitleBar_CloseButton" Command="{Binding CloseCommand}" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,5,5,0" Width="20" Height="20" Cursor="Hand" Background="#FFFF0000" Foreground="#FF212121" />
<!-- ContentPresenter -->
<ContentPresenter Grid.Row="1" />
<!-- ... snip ... -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="WindowBorder" Property="Background" Value="Blue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="WindowBorder" Property="Background" Value="#22000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Inside the PseudoWindowContainer.cs class I create some event handlers to listen for MouseDown/MouseUp/MoveMove events:
public override void OnApplyTemplate()
{
_titleBar = (Border)Template.FindName("PART_TitleBar", this);
if (_titleBar != null)
{
_titleBar.MouseDown += TitleBar_MouseDown;
_titleBar.MouseUp += TitleBar_MouseUp;
}
_grip = (ResizeGrip)Template.FindName("PART_ResizeGrip", this);
if (_grip != null)
{
_grip.MouseLeftButtonDown += ResizeGrip_MouseLeftButtonDown;
_grip.MouseLeftButtonUp += ResizeGrip_MouseLeftButtonUp;
}
base.OnApplyTemplate();
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove += TitleBar_MouseMove;
((Border)sender).CaptureMouse();
_windowLocation.X = Left;
_windowLocation.Y = Top;
_clickLocation = this.PointToScreen(Mouse.GetPosition(this));
}
private void TitleBar_MouseUp(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove -= TitleBar_MouseMove;
((Border)sender).ReleaseMouseCapture();
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
Point currentLocation = this.PointToScreen(Mouse.GetPosition(this));
Left = _windowLocation.X + currentLocation.X - _clickLocation.X;
Top = _windowLocation.Y + currentLocation.Y - _clickLocation.Y;
}
The trouble I run into is the "Left" and "Top" are not defined properties, and updating them to Canvas.SetLeft/SetTop (or GetLeft/GetTop, accordingly) does not update the position on the Canvas.
I have "Left" and "Top" defined in the ViewModel of the controls I place into the ListBoxItems, and are thus subsequently wrapped with a PseudoWindowContainer because of the Template. These values are being honored and the objects do appear in the correct location when the application comes originally.
I believe I need to somehow define "Left" and "Top" in my PseudoWindowContainer (aka: ContentControl) and have them propagate back up to my ViewModel. Is this possible?
Thanks again for any help!
I've found a solution to the problem. Instead of typing it all out again, I will point to the MSDN Forum post that describes what I did:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/d9036b30-bc6e-490e-8f1e-763028a50153
Did you read article by Bea Stollnitz: The power of Styles and Templates in WPF?
That is an example of Listbox where items are drawn at specific coordinates calculated in Converter.
I have the following style:
<Style x:Key="ActionLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Margin" Value="10,3" />
<Setter Property="Padding" Value="0" />
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="FontFamily" Value="Calibri" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="TextBlock.TextDecorations" Value="Underline" />
</MultiTrigger>
</Style.Triggers>
</Style>
So basically, I want to have a label which is underlined when it is enabled and the mouse cursor is over it. The part of this style which is not working is the <Setter Property="TextBlock.TextDecorations" Value="Underline" />. Now, what am I doing wrong here? Thanks for all the help.
This is actually much more difficult than it appears. In WPF, a Label is not a TextBlock. It derives from ContentControl and can therefore host other, non-text controls in its Content collection.
However, you can specify a string as the content as in the example below. Internally, a TextBlock will be constructed to host the text for you.
<Label Content="Test!"/>
This internally translates to:
<Label>
<Label.Content>
<TextBlock>
Test!
</TextBlock>
</Label.Content>
</Label>
The simple solution to this would be for the TextDecorations property of a TextBlock to be an attached property. For example, FontSize is designed this way, so the following works:
<Label TextBlock.FontSize="24">
<Label.Content>
<TextBlock>
Test!
</TextBlock>
</Label.Content>
</Label>
The TextBlock.FontSize attached property can be applied anywhere in the visual tree and will override the default value for that property on any TextBlock descendant in the tree. However, the TextDecorations property is not designed this way.
This leaves you with at least a few options.
Use color, border, cursor, etc., instead of underlined text because this is 100% easier to implement.
Change the way you are doing this to apply the Style to the TextBlock instead.
Go to the trouble to create your own attached property and the control template to respect it.
Do something like the following to nest the style for TextBlocks that appear as children of your style:
FYI, this is the ugliest thing I've done in WPF so far, but it works!
<Style x:Key="ActionLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Margin" Value="10,3" />
<Setter Property="Padding" Value="0" />
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="FontFamily" Value="Calibri" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Red" />
</MultiTrigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Label}, Path=IsMouseOver}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="TextDecorations" Value="Underline"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
This works because it is overriding the default style of any TextBlock beneath a Label of this style. It then uses a MultiDataTrigger to allow relative binding back up to the Label to check if its IsMouseOver property is True. Yuck.
Edit:
Note that this only works if you explicitly create the TextBlock. I was incorrect when I posted this because I had already dirtied up my test Label. Boo. Thanks, Anvaka, for pointing this out.
<Label Style="{StaticResource ActionLabelStyle}">
<TextBlock>Test!</TextBlock>
</Label>
This works, but if you have to go to this trouble, you're just working too hard. Either someone will post something more clever, or as you said, my option 1 is looking pretty good right now.
Further to Jerry's answer, in order to avoid having to add the TextBlock into the template each time you can let the style do this too by adding the Setter property into the style:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<TextBlock>
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
Then your Label is back to:
<Label Content="Test!" Style="{StaticResource ActionLabelStyle}" />
Thanks Jerry!
Andrew.
I think the issue is that TextBlock.TextDecorations is not defined on Label.
You can use this approach if you're happy to use a TextBlock rather than a Label.
Just to add my workaround to the mix. I am currently using C# code and it works well enough. I just trap the MouseLeave and MouseEnter events and show the underline there.
void Control_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
WPFHelper.EnumerateChildren<TextBlock>(this, true).ForEach(c => c.TextDecorations = null);
}
void Control_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
WPFHelper.EnumerateChildren<TextBlock>(this, true).ForEach(c => c.TextDecorations = TextDecorations.Underline);
}
The WPFHelper class simply enumerates all the children of an DependencyObject and ForEach is an extension method that just does executes the action inside the lambda expression for each item.
An old question, but since I just fought with this, heres my method. Though it just uses a Trigger as opposed to a MultiTrigger
For XAML:
<Label Content="This text is for testing purposes only.">
<Style TargetType="{x:Type ContentPresenter}">
<Style.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextDecorations" Value="Underline" />
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</Label>
For C# Codebehind:
public override void OnApplyTemplate()
{
if( !hasInitialized )
{
var tbUnderStyle = new Style( typeof(TextBlock), (Style)FindResource(typeof(TextBlock)) );
var tbUnderSetter = new Setter( TextBlock.TextDecorationsProperty, TextDecorations.Underline );
var tbUnderTrigger = new Trigger() { Property = Label.IsMouseOverProperty, Value = true };
tbUnderTrigger.Setters.Add( tbUnderSetter );
var contentPresenter = FindVisualChild<ContentPresenter>( this );
contentPresenter.Resources.Add( typeof(TextBlock), tbUnderStyle );
hasInitialized = true;
}
}
hasInitialized being a bool that is set to false in the constructor, and FindVisualChild sourced from here.