Replacing DependencyProperty by AttachedProperty in class hierarchy - is that possible? - c#

I want some of my Button, ToggleButton and RadioButton to have a Geometry property so that I can use in ControlTemplates to avoid boilerplate when assigning instance-specific Geometries to those controls.
For example, currently I can write this:
<my:GeometryButton Geometry="{StaticResource OneGeometry}"/>
<my:GeometryButton Geometry="{StaticResource OtherGeometry}"/>
<!-- ...and inside the Style for GeometryButton: -->
<ContentControl TargetType="{x:Type my:GeometryButton}">
<Border>
<Path Data={TemplateBinding Geometry}/>
</Border>
</ContentControl>
With this GeometryButton class:
public class GeometryButton : Button
{
static GeometryButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GeometryButton),
new FrameworkPropertyMetadata(typeof(GeometryButton)));
}
public Geometry Geometry
{
get { return (Geometry)GetValue(GeometryProperty); }
set { SetValue(GeometryProperty, value); }
}
public static readonly DependencyProperty GeometryProperty =
DependencyProperty.Register("Geometry",
typeof(Geometry),
typeof(GeometryButton),
new PropertyMetadata(default(Geometry)));
}
The problem is, if I am to define GeometryToggleButton and GeometryRadioButton classes, I am supposed to repeat the DependencyProperty code in each class, violating DRY.
Also, since RadioButton derives from ToggleButton, and the later and Button in turn derive from ButtonBase, I think I could take advantage of this, but if I need to inherit from each class separately, I don't benefit from inheritance at all.
So I considered to use AttachedProperties, but the tutorials and examples usually mention examples like DockPanel.Dock, Grid.Left, or Control.Foreground, implying the existence of some "Parent", so I am not sure of:
Does the AttachedProperties concept applies to my use case in the first place?
If yes, how am I supposed to implement it?

Create a regular attached property. In your control templates, use it.
No seriously, there's not a lot more to it than that.
For example, I wrote an attached CornerRadius property so that many different control styles could specify a CornerRadius, which would be used by their templates.
public static class Attached
{
public static readonly DependencyProperty CornerRadiusProperty
= DependencyProperty.RegisterAttached(
"CornerRadius",
typeof(CornerRadius),
typeof(Attached),
new FrameworkPropertyMetadata(
new CornerRadius(0),
FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
PropertyChanged)
);
public static CornerRadius GetCornerRadius(DependencyObject uie)
{
return (CornerRadius)uie.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject uie, CornerRadius value)
{
uie.SetValue(CornerRadiusProperty, value);
}
}
XAML
N.B. The parens around (edcorpext:Attached.CornerRadius) in the Binding are critical, so it understands that the string is one indivisible path segment referring to an attached property; otherwise it tries to parse it as a path to a property of Binding.Source, hits the :, and throws an exception.
<ControlTemplate x:Key="EdCorpButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<Border
x:Name="PART_BackgroundBorder"
CornerRadius="{Binding (edcorpext:Attached.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1.3"
BorderBrush="{StaticResource ControlBorderBrush}"
Background="{StaticResource EdCorpGrayMediumGradientBrush}"
SnapsToDevicePixels="True"
/>
<!-- etc. etc. etc. -->
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="edcorpext:Attached.CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
<Setter Property="Template" Value="{StaticResource EdCorpButtonTemplate}" />
<!-- etc. etc. etc. -->
They told me we needed the UI on this application to look "more modern" and since we don't have a real designer who knows what he's doing, I put asymmetrical rounded corners on stuff. It was actually a lot worse before.

Related

Avalonia style selector doen't works on derived classes

I'm having an issue to style my custom control derived from button.
I inherited the Button class to add a DependencyProperty to it:
public class IconButton : Button, IStyleable
{
Type IStyleable.StyleKey => typeof(Button);
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<IconButton, string>(nameof(Icon));
public string Icon
{
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
Now I want to create a style targetting only this derived class:
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:cc="*****.*****.CustomControls">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical">
<cc:IconButton Content="hello custom" Icon="fab fa-github"/>
<Button Content="hello"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector=":is(cc|IconButton)">
<Setter Property="Background" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<i:Icon Value="{TemplateBinding Icon}" />
<TextBlock Text="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>
AS you can see, the content is not my control template as the icon is not visible and the background is not red. Therefore I can't use my dependencyproperty.
Any suggestions or is it not supported by the language?
I read on the documentation that is(****) for selector is intended to support derived type so I would expect my code to be working :(
Thx for help.
Try to remove IStyleable and your overriden StyleKey. You are telling the selector that it should look for a Button style and not your style.
Happy coding
Tim
PS I cannot test it on my own right now, so if it doesn't work please tell me here.
Update: I just made a blank new project and tested your code. Once I remove the StyleKey, it works:
This is the modified IconButton:
public class IconButton : Button
{
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<IconButton, string>(nameof(Icon));
public string Icon
{
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
If you are able to upload a minimal sample on Github, I can have a look what may be wrong.
Update 2: Looking into your demo App I see that you use a third party icon lib. The lib requires you to configure a special service at start up, see: https://github.com/Projektanker/Icons.Avalonia#1-register-icon-providers-on-app-start-up
I send you a PR which fixes your issue.
Happy coding
Tim

How to use TemplateBinding in Path.Data?

How can I bind a property inside Path.Data to a TemplateBinding? I noticed that in this following example, the properties SegmentColor and StrokeThickness are set and updated correctly, but not the property TargetPoint. Further testing seems to confirm the issue seems to be related to the property being nested in an element of Path.Data. The following code tries to simplify the context which I am facing while creating the template for a custom control.
C#:
public class TestProgressBar : ProgressBar
{
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public Point TargetPoint
{
get { return (Point)GetValue(TargetPointProperty); }
set { SetValue(TargetPointProperty, value); }
}
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register(nameof(StrokeThickness), typeof(double), typeof(TestProgressBar), new PropertyMetadata());
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register(nameof(SegmentColor), typeof(Brush), typeof(TestProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
public static readonly DependencyProperty TargetPointProperty =
DependencyProperty.Register(nameof(TargetPoint), typeof(Point), typeof(TestProgressBar), new PropertyMetadata());
}
Xaml:
<c:TestProgressBar StrokeThickness="15"
TargetPoint="100,0">
<c:TestProgressBar.Style>
<Style TargetType="{x:Type c:TestProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:TestProgressBar}">
<Grid>
<Path
Stroke="{TemplateBinding SegmentColor}"
StrokeThickness="{TemplateBinding StrokeThickness}"
Width="100" Height="100">
<Path.Data>
<PathGeometry>
<PathFigure>
<LineSegment Point="{TemplateBinding TargetPoint}"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</c:TestProgressBar.Style>
</c:TestProgressBar>
The issue is a little known detail about TemplateBinding, they do not work on properties Freezables. In fact, a LineSegment indirectly derives from Freezable as you can see in its inheritance hierarchy.
Object -> DispatcherObject -> DependencyObject -> Freezable -> Animatable -> PathSegment -> LineSegment
However, Path does not and that is why TemplateBindings on its properties work. As a template binding is just an optimized but limited version of a Binding, you can always use its binding syntax equivalent that does not come with any limitations and will also work on Freezables.
<LineSegment Point="{Binding TargetPoint, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
Note, that the Mode is set to OneWay here, to point out that TemplateBindings are always one-way and this is the equivalent Binding, but bindings are more powerful and support any binding mode.
I am using readonly dependency properties, so the issue can't be linked to TwoWay binding problems (as I've hard can be the case with TemplateBinding).
A dependency property declaration must be read-only, but that does not make the dependency property read-only itself, that is done by declaring a dependency property key and using the RegisterReadOnly method.
Read-Only Dependency Properties / Creating Custom Read-Only Dependency Properties
It seems that using TemplateBindings within the Path.Data causes the issue. Replacing it with a TemplatedParent Binding fixes the issue:
<LineSegment Point="{Binding TargetPoint, RelativeSource={RelativeSource TemplatedParent}}"/>
I can't quite explain why this is, though. In my original code, I am using readonly dependency properties, so the issue can't be linked to TwoWay binding problems (as I've hard can be the case with TemplateBinding).
I know that Binding using TemplatedParent is runtime-evaluated, as opposed to compile-time for TemplateBinding, so perhaps something along those lines fixes the binding.

XAML combine styles with styles from toolkit [duplicate]

In WPF, how would I apply multiple styles to a FrameworkElement? For instance, I have a control which already has a style. I also have a separate style which I would like to add to it without blowing away the first one. The styles have different TargetTypes, so I can't just extend one with the other.
I think the simple answer is that you can't do (at least in this version of WPF) what you are trying to do.
That is, for any particular element only one Style can be applied.
However, as others have stated above, maybe you can use BasedOn to help you out. Check out the following piece of loose xaml. In it you will see that I have a base style that is setting a property that exists on the base class of the element that I want to apply two styles to. And, in the second style which is based on the base style, I set another property.
So, the idea here ... is if you can somehow separate the properties that you want to set ... according the inheritance hierarchy of the element you want to set multiple styles on ... you might have a workaround.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50"/>
</Grid>
</Page>
Note:
One thing in particular to note. If you change the TargetType in the second style (in first set of xaml above) to ButtonBase, the two Styles do not get applied. However, check out the following xaml below to get around that restriction. Basically, it means you need to give the Style a key and reference it with that key.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
</Grid>
</Page>
Bea Stollnitz had a good blog post about using a markup extension for this, under the heading "How can I set multiple styles in WPF?"
That blog is dead now, so I'm reproducing the post here:
WPF and Silverlight both offer the ability to derive a Style from
another Style through the “BasedOn” property. This feature enables
developers to organize their styles using a hierarchy similar to class
inheritance. Consider the following styles:
<Style TargetType="Button" x:Key="BaseButtonStyle">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red" />
</Style>
With this syntax, a Button that uses RedButtonStyle will have its
Foreground property set to Red and its Margin property set to 10.
This feature has been around in WPF for a long time, and it’s new in
Silverlight 3.
What if you want to set more than one style on an element? Neither WPF
nor Silverlight provide a solution for this problem out of the box.
Fortunately there are ways to implement this behavior in WPF, which I
will discuss in this blog post.
WPF and Silverlight use markup extensions to provide properties with
values that require some logic to obtain. Markup extensions are easily
recognizable by the presence of curly brackets surrounding them in
XAML. For example, the {Binding} markup extension contains logic to
fetch a value from a data source and update it when changes occur; the
{StaticResource} markup extension contains logic to grab a value from
a resource dictionary based on a key. Fortunately for us, WPF allows
users to write their own custom markup extensions. This feature is not
yet present in Silverlight, so the solution in this blog is only
applicable to WPF.
Others
have written great solutions to merge two styles using markup
extensions. However, I wanted a solution that provided the ability to
merge an unlimited number of styles, which is a little bit trickier.
Writing a markup extension is straightforward. The first step is to
create a class that derives from MarkupExtension, and use the
MarkupExtensionReturnType attribute to indicate that you intend the
value returned from your markup extension to be of type Style.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Specifying inputs to the markup extension
We’d like to give users of our markup extension a simple way to
specify the styles to be merged. There are essentially two ways in
which the user can specify inputs to a markup extension. The user can
set properties or pass parameters to the constructor. Since in this
scenario the user needs the ability to specify an unlimited number of
styles, my first approach was to create a constructor that takes any
number of strings using the “params” keyword:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
My goal was to be able to write the inputs as follows:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Notice the comma separating the different style keys. Unfortunately,
custom markup extensions don’t support an unlimited number of
constructor parameters, so this approach results in a compile error.
If I knew in advance how many styles I wanted to merge, I could have
used the same XAML syntax with a constructor taking the desired number
of strings:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
As a workaround, I decided to have the constructor parameter take a
single string that specifies the style names separated by spaces. The
syntax isn’t too bad:
<Button Style="{local:MultiStyle BigButtonStyle GreenButtonStyle}" … />
private string[] resourceKeys;
public MultiStyleExtension(string inputResourceKeys)
{
if (inputResourceKeys == null)
{
throw new ArgumentNullException("inputResourceKeys");
}
this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (this.resourceKeys.Length == 0)
{
throw new ArgumentException("No input resource keys specified.");
}
}
Calculating the output of the markup extension
To calculate the output of a markup extension, we need to override a
method from MarkupExtension called “ProvideValue”. The value returned
from this method will be set in the target of the markup extension.
I started by creating an extension method for Style that knows how to
merge two styles. The code for this method is quite simple:
public static void Merge(this Style style1, Style style2)
{
if (style1 == null)
{
throw new ArgumentNullException("style1");
}
if (style2 == null)
{
throw new ArgumentNullException("style2");
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
// This code is only needed when using DynamicResources.
foreach (object key in style2.Resources.Keys)
{
style1.Resources[key] = style2.Resources[key];
}
}
With the logic above, the first style is modified to include all
information from the second. If there are conflicts (e.g. both styles
have a setter for the same property), the second style wins. Notice
that aside from copying styles and triggers, I also took into account
the TargetType and BasedOn values as well as any resources the second
style may have. For the TargetType of the merged style, I used
whichever type is more derived. If the second style has a BasedOn
style, I merge its hierarchy of styles recursively. If it has
resources, I copy them over to the first style. If those resources are
referred to using {StaticResource}, they’re statically resolved before
this merge code executes, and therefore it isn’t necessary to move
them. I added this code in case we’re using DynamicResources.
The extension method shown above enables the following syntax:
style1.Merge(style2);
This syntax is useful provided that I have instances of both styles
within ProvideValue. Well, I don’t. All I get from the constructor is
a list of string keys for those styles. If there was support for
params in the constructor parameters, I could have used the following
syntax to get the actual style instances:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
But that doesn’t work. And even if the params limitation didn’t exist,
we would probably hit another limitation of markup extensions, where
we would have to use property-element syntax instead of attribute
syntax to specify the static resources, which is verbose and
cumbersome (I explain this bug better in a previous blog
post).
And even if both those limitations didn’t exist, I would still rather
write the list of styles using just their names – it is shorter and
simpler to read than a StaticResource for each one.
The solution is to create a StaticResourceExtension using code. Given
a style key of type string and a service provider, I can use
StaticResourceExtension to retrieve the actual style instance. Here is
the syntax:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Now we have all the pieces needed to write the ProvideValue method:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
Here is a complete example of the usage of the MultiStyle markup
extension:
<Window.Resources>
<Style TargetType="Button" x:Key="SmallButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="Button" x:Key="GreenButtonStyle">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style TargetType="Button" x:Key="BoldButtonStyle">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Window.Resources>
<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />
But you can extend from another.. take a look at the BasedOn property
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="3" />
</Style>
<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
WPF/XAML doesn't provide this functionality natively, but it does provide the extensibility to allow you to do what you want.
We ran into the same need, and ended up creating our own XAML Markup Extension (which we called "MergedStylesExtension") to allow us to create a new Style from two other styles (which, if needed, could probably be used multiple times in a row to inherit from even more styles).
Due to a WPF/XAML bug, we need to use property element syntax to use it, but other than that it seems to work ok. E.g.,
<Button
Content="This is an example of a button using two merged styles">
<Button.Style>
<ext:MergedStyles
BasedOn="{StaticResource FirstStyle}"
MergeStyle="{StaticResource SecondStyle}"/>
</Button.Style>
</Button>
I recently wrote about it here:
http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
This is possible by creating a helper class to use and wrap your styles. CompoundStyle mentioned here shows how to do it. There are multiple ways, but the easiest is to do the following:
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
Hope that helps.
Use AttachedProperty to set multiple styles like following code:
public static class Css
{
public static string GetClass(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(ClassProperty);
}
public static void SetClass(DependencyObject element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(ClassProperty, value);
}
public static readonly DependencyProperty ClassProperty =
DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css),
new PropertyMetadata(null, OnClassChanged));
private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ui = d as FrameworkElement;
Style newStyle = new Style();
if (e.NewValue != null)
{
var names = e.NewValue as string;
var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in arr)
{
Style style = ui.FindResource(name) as Style;
foreach (var setter in style.Setters)
{
newStyle.Setters.Add(setter);
}
foreach (var trigger in style.Triggers)
{
newStyle.Triggers.Add(trigger);
}
}
}
ui.Style = newStyle;
}
}
Usage: (Point the xmlns:local="clr-namespace:style_a_class_like_css" to the right namespace)
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:style_a_class_like_css"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="325">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Red" >
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="TextBlock" x:Key="Green" >
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style TargetType="TextBlock" x:Key="Size18" >
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="6"/>
</Style>
<Style TargetType="TextBlock" x:Key="Bold" >
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<StackPanel>
<Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
<Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
<Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>
</StackPanel>
</Window>
Result:
if you are not touching any specific properties, you can get all base and common properties to the style which's target type would be FrameworkElement. then, you can create specific flavours for each target types you need, without need of copying all those common properties again.
You can probably get something similar if applying this to a collection of items by the use of a StyleSelector, i have used this to approach a similar problem in using different styles on TreeViewItems depending on the bound object type in the tree. You may have to modify the class below slightly to adjust to your particular approach but hopefully this will get you started
public class MyTreeStyleSelector : StyleSelector
{
public Style DefaultStyle
{
get;
set;
}
public Style NewStyle
{
get;
set;
}
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);
//apply to only the first element in the container (new node)
if (item == ctrl.Items[0])
{
return NewStyle;
}
else
{
//otherwise use the default style
return DefaultStyle;
}
}
}
You then apply this as so
<TreeView>
<TreeView.ItemContainerStyleSelector
<myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
NewStyle="{StaticResource NewItemStyle}" />
</TreeView.ItemContainerStyleSelector>
</TreeView>
Sometimes you can approach this by nesting panels. Say you have a Style which changes Foreground and another changes FontSize, you can apply the latter one on a TextBlock, and put it in a Grid which its Style is the first one. This might help and might be the easiest way in some cases, though it won't solve all the problems.
When you override SelectStyle you can get GroupBy property via reflection like below:
public override Style SelectStyle(object item, DependencyObject container)
{
PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
{
return this.TitleStyle;
}
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
{
return this.DateStyle;
}
return null;
}
If you are trying to apply a unique style to just one single element as an addition to a base style, there is a completely different way to do this that is IMHO much better for readable and maintainable code.
It's extremely common to need to tweak parameters per individual element. Defining dictionary styles just for use on one-element is extremely cumbersome to maintain or make sense of. To avoid creating styles just for one-off element tweaks, read my answer to my own question here here:
https://stackoverflow.com/a/54497665/1402498

Dependency Property Binding Not Updating Target

I have a custom dependency property:
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("HeaderProperty", typeof(string), typeof(RadAdjustableSlider));
public string Header
{
get
{
return (string)GetValue(HeaderProperty);
}
set
{
SetValue(HeaderProperty, value);
}
}
I then have a binding in my xaml:
<TextBlock Name="txtHeader" Text="{Binding ElementName=main, Path=Header, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Note that I also have this in the declaration at the top of the xaml file:
x:Name="main"
Finally, I have this constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
}
When I put this control inside of another parent control, the Header textblock is blank. Why?
Edit: This blog says that the correct way to do this is by providing a ValidateValueCallback in the DependencyProperty.Register call, but that seems like quite a bit of plumbing and doesn't explain the way dependency properties behave when interacting with external controls. Am I really going to have to write callback functions for all of my dependency properties?
There is a HeaderedContentControl and HeaderedItemsControl in the framework already...
But if you really want to create your own then you should probably use a TemplateBinding. Try something like this instead:
class MyHeaderedControl : ContentControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(object),
typeof(MyHeaderedControl),
new PropertyMetadata());
public MyHeaderedControl()
{
this.DefaultStyleKey = typeof(MyHeaderedControl);
}
}
Then in your project create a file at "\Themes\Generic.xaml". This is a specially named file and must be in the root of the project then in the Themes folder. It must contain a ResourceDictionary.
<ResourceDictionary
xmlns="..."
xmlns:x="..."
xmlns:c="MyControlLibrary1"
>
<Style TargetType="{x:Type c:MyHeaderedControl>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MyHeaderedControl}">
<StackPanel>
<ContentControl Content="{TemplateBinding Header}" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Also, in your AssemblyInfo.cs add this attribute if it's not there already:
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
So for the overview. The general idea is to create some type of logical control where you have properties and events and logic etc. Then in the same assembly you provide default themes. That is how the controls will be displayed by default. At any place where the controls are used the default templates can be overriden and specific templates can be overridden as usual.
So this is the most pain free way you can add custom content like this to your custom controls! Try it once and it will make sense and not feel to cludgy. If you make more controls just keep adding them to the Generic.xaml file.
As justin.m.chase mentioned above, a custom control is probably the best way to go but UserControls are a common scenario so I'll add my 2c anyway.
A UserControl does not set the DataContent property for you and therefore all your bindings inside your UserControl XAML resolve to the DataContent of where you placed the control.
To change this behaviour, either set the DataContext property inside your usercontrol constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
this.DataContext = this;
}
and then bind like this:
<TextBlock Text="{Binding Header}" />
or don't set the DataContext and bind like this:
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ns:RadAdjustableSlider}}}" />

Passing parameters to a template

Say I have defined a button with rounded corners.
<Style x:Key="RoundButton" TargetType="Button">
<!-- bla bla -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="0,5,5,0" />
<!-- bla bla -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I it possible that the user of this button can specify the CornerRadius? Can I use a TemplateBinding? But where should I bind to? (to Tag?)
In addition to Kent's suggestions, you could also create an attached property to define the CornerRadius on the button, and bind to that property in the template
In order to use a TemplateBinding, there must be a property on the templated control (Button, in this case). Button does not have a CornerRadius or equivalent property, so your options are:
hard code the value in the template
Hijack another property (such as Tag) to store this information. This is quicker, but lacks type safety, is harder to maintain, and prevents other uses of that property.
Subclass Button and add the propery you need, then provide a template for that subclass. This takes a little longer but yields a much nicer experience for consumers of your control.
The button type doesn't have a property for CornerRadius, so templating this won't be possible. I think the easiest way is creating a new class which inherits from Button and add a new dependency property for the CornerRadius. Like this:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication3
{
public class RoundedButton:Button
{
public CornerRadius CornerRadius
{
get { return (CornerRadius) GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof (CornerRadius),
typeof (RoundedButton), new UIPropertyMetadata());
}
}
In xaml you can use it like:
<Local:RoundedButton
Style="{DynamicResource RoundButton}"
Width="64" Height="32"
Content="Hello"
CornerRadius="1,5,10,5"
Background="#FF9CFFD5" />
A template binding to the CornerRadius will work without a problem now.

Categories