How to override base style control template in derived style in WPF? - wpf

I have a style for button. That style contains the ControlTemplate for Button. The ControlTemplate contains an Image with name "ImgButton".
I want to make this style as base style for other Buttons and want to override the "Source" property of Image control for different buttons.
Any ideas?

You may create attached behavior that will offer a property to assign Source. You should bind your image to this property in a template using TemplatedParent as RelativeSource. In derived styles you can simply use Setter(s) to specify a different Source.
Attached behavoir:
public static class ImageSourceBehavior
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(ImageSource), typeof(ImageSourceBehavior),
new FrameworkPropertyMetadata(null));
public static ImageSource GetSource(DependencyObject dependencyObject)
{
return (ImageSource)dependencyObject.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject dependencyObject, ImageSource value)
{
dependencyObject.SetValue(SourceProperty, value);
}
}
Styles:
<Style x:Key="Style1"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Image Source="{Binding Path=(local:ImageSourceBehavior.Source),RelativeSource={RelativeSource TemplatedParent}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Style2"
BasedOn="{StaticResource Style1}"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
</Style>

Related

Create a custom ListViewItem (subclass ListViewItem)

I derived a class from ListViewItem, it has some custom dependency properties:
public class CustomListViewItem : ListViewItem
{
public static DependencyProperty CustomDependencyProperty;
...
}
There is also a ControlTemplate for this class.
<Style TargetType="{x:Type local:CustomListViewItem}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListViewItem}">
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Now I want to use this CustomListViewItem in a ListView instead of ListViewItem. But when I try to do something like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type local:CustomListViewItem}">
...
</Style>
</ListView.ItemContainerStyle>
compiler says: "A style intended for type 'CustomItem' cannot be applied to type 'ListViewItem".
I know that I can use ControlTemplate with ListViewItem TargetType to customize ItemContainerStyle or DataTemplate to customize ItemTemplate, but how can I subclass ListViewItem to substitute my own Item type?
Any help will be appreciated.
I found the answer after considering this question. The core idea is that it is necessary to create not only a custom ListViewItem, but also a custom ListView and override GetContainerForItemOverride() method for it:
public class CustomListView : ListView
{
static CustomListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListView), new FrameworkPropertyMetadata(typeof(CustomListView)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomListViewItem();
}
}
Of course, it's also necessary to provide a proper ControlTemplate for a CustomListView.
Also PrepareContainerForItemOverride method will be useful.

WPF Custom Control Property Setter

I have a very simple Button-based control that displays an ellipse with color taken from custom dependency control called "Brush".
The template displays ellipse with proper color, but the Setters in Trigger do not recognize the "Brush" property (errors highlighted in the XAML file below).
How to access the "Brush" property in the setter so I can change its value on MouseOver?
XAML:
<Button x:Class="WpfTest.EllipseButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTest"
Style="{DynamicResource localStyle}"
Name="ellipseButton">
<Button.Resources>
<Style x:Key="localStyle"
TargetType="local:EllipseButton">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Fill="{Binding ElementName=ellipseButton, Path=Brush}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<!-- ERROR HERE: The property "Brush" is not a dependency property. -->
<Setter Property="Brush"
Value="Blue" />
<!-- ERROR HERE: The "BrushProperty" is not recognized or is not accessible. -->
<Setter Property="BrushPropety"
Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Resources>
<Grid>
</Grid>
</Button>
code-behind:
public partial class EllipseButton : Button
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
public Brush Brush
{
get
{
return (Brush)GetValue(BrushProperty);
}
set
{
SetValue(BrushProperty, value);
}
}
public EllipseButton()
{
InitializeComponent();
}
}
Your property is called "Fill" not "Brush":
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Fill", //Error is here
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
Change that to:
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Brush",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
Okay, several things needed to be done:
1) Rename dependency property name to "Brush" (it was wrongly named "Fill") - thanks to HighCore
2) When the control is used in code, remove setting the "Brush" property - the local value overriden the setters from style.
3) Move the style from custom control to higher level (e.g. under "Themes\Generic.xaml")
4) Remove x:Key attribute from the style and keep just the type name (still don't know why...)
5) Add default value of the "Brush" property to the style setter (again, not sure why...)
Fixed EllipseButton.xaml:
<Button x:Class="WpfTest.EllipseButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid/>
</Button>
fixed code-behind:
public partial class EllipseButton
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Brush",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(null));
public Brush Brush
{
get
{
return (Brush)GetValue(BrushProperty);
}
set
{
SetValue(BrushProperty, value);
}
}
public EllipseButton()
{
InitializeComponent();
}
}
fixed style (Generic.xaml):
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTest">
<Style TargetType="local:EllipseButton">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Fill="{Binding ElementName=ellipseButton, Path=Brush}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Brush" Value="Pink"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Brush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>

Silverlight element to element binding in generic.xaml

Is there a way to have element to element binding in Silverlight templated controls?
Example: I have two custom controls, SomeControl and CustomSlider. SomeControl has a dependency property called someValue. I want to bind the value of CustomSlider to this property, so my generic.xaml file looks like this:
<Style TargetType="local:SomeControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SomeControl">
<...>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:CustomSlider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomSlider">
<Slider Value="{Binding someValue, ElementName=local:SomeControl}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and this is my dependency property:
public int someValue,
{
get { return (int)GetValue(someValueProperty); }
set { SetValue(someValueProperty, value); }
}
public static readonly DependencyProperty (someValueProperty) =
DependencyProperty.Register(someValue); typeof(int), typeof(SomeControl,
new PropertyMetadata(0));
This throws an "BindingExpression_CannotFindElementName" exception.
You can't use it like this. A binding through ElementName should be used to specific element instance, not style. You can create other dependency property, say SliderValue in your CustomSlidercontrol and bind to it.
<local:SomeControl x:Name="SomeControl"/>
<local:CustomSlider SliderValue="{Binding someValue, ElementName=SomeControl}"/>
And change your Slider Value from template when your SliderValue property changes;

WPF Setting on a Control Template

General question. I have a ControlTemplate that is reasonably complex. Several TextBoxes etc.
I can't use TemplateBinding to bring all the properties to the surface so that I can set all the styles.
Is there a way for a Style to 'delv' into the controls within a control to set values?
Hope my question is clear without an example.
Thanks
The short answer is no. The ControlTemplate is essentially a black box, at least where XAML is concerned (there are ways to dig down the visual tree in code).
When you say you "can't use TemplateBinding", why not? If you just don't have enough available properties that can be fixed by creating some attached properties for the values you want to pass through. This is assuming you're templating a control that you can't change, otherwise you can just add new dependency properties.
Attached property:
public static class CustomProps
{
public static readonly DependencyProperty MyNewBrushProperty = DependencyProperty.RegisterAttached(
"MyNewBrush",
typeof(Brush),
typeof(CustomProps),
new UIPropertyMetadata(Brushes.Green));
public static Brush GetMyNewBrush(DependencyObject target)
{
return (Brush)target.GetValue(MyNewBrushProperty);
}
public static void SetMyNewBrush(DependencyObject target, Brush value)
{
target.SetValue(MyNewBrushProperty, value);
}
}
And usage in Style and Template:
<Style TargetType="{x:Type Button}">
<Setter Property="local:CustomProps.MyNewBrush" Value="Red" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:CustomProps.MyNewBrush)}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Using this method also still allows overriding values on individual instances.

Style Trigger on Attached Property

I have created my own Attached Property like this:
public static class LabelExtension
{
public static bool GetSelectable(DependencyObject obj)
{
return (bool)obj.GetValue(SelectableProperty);
}
public static void SetSelectable(DependencyObject obj, bool value)
{
obj.SetValue(SelectableProperty, value);
}
// Using a DependencyProperty as the backing store for Selectable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectableProperty =
DependencyProperty.RegisterAttached("Selectable", typeof(bool), typeof(Label), new UIPropertyMetadata(false));
}
And then I'm trying to create a style with a trigger that depends on it:
<!--Label-->
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<Trigger Property="Util:LabelExtension.Selectable" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<TextBox IsReadOnly="True" Text="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
But I'm getting a run time exception:
Cannot convert the value in attribute 'Property' to object of type 'System.Windows.DependencyProperty'. Error at object 'System.Windows.Trigger' in markup file
How can I access the value of the attached property in a style trigger? I have tried using a DataTrigger with a RelativeSource binding but it wasn't pulling the value through.
Your trigger declaration is fine, but your attached property declaration has a glitch. The owner type of a dependency property needs to be the type that declares it, not the type you plan to attach it to. So this:
DependencyProperty.RegisterAttached("Selectable", typeof(bool), typeof(Label)...
needs to change to this:
DependencyProperty.RegisterAttached("Selectable", typeof(bool), typeof(LabelExtension)...
^^^^^^^^^^^^^^^^^^^^^^

Resources