How to use TemplateBinding in Path.Data? - c#

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.

Related

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

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.

Logical errors while simulating master-page concept in WPF

I'm trying to simulate the ASP.Net master-page concept (Layout in MVC) on WPF Windows.
I have a CustomWindow class that specifies some behaviors for this sort of Windows:
public class CustomWindow : Window
{
//...
}
And MasterWindowBase; a CustomWindow that takes some sort of my UserControls to be the window content (via style):
public abstract class MasterWindowBase : CustomWindow
{
public MasterWindowBase(MyUserControlBase content)
{
ContentUserControl = content;
Style = Application.Current.FindResource("MasterWindowStyle") as Style;
}
#region ContentUserControl Property
public MyUserControlBase ContentUserControl
{
get { return (MyUserControlBase)GetValue(ContentUserControlProperty); }
set { SetValue(ContentUserControlProperty, value); }
}
public static readonly DependencyProperty ContentUserControlProperty =
DependencyProperty.Register("ContentUserControl", typeof(MyUserControlBase), typeof(MasterWindowBase));
#endregion
}
The style defined in application resources:
<Style x:Key="MasterWindowStyle" TargetType="{x:Type local:MasterWindowBase}">
<Setter Property="Content">
<Setter.Value>
<Grid>
<StackPanel>
<TextBlock Text="This is a master window"/>
<ContentPresenter Content="{Binding ContentUserControl, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MasterWindowBase}}}"/>
</StackPanel>
</Grid>
</Setter.Value>
</Setter>
</Style>
And MasterWindow; a generic window used to create an instance of MasterWindowBase with the specified UserControl type:
public class MasterWindow<TMyUserControlBase>
: MasterWindowBase
where TMyUserControlBase : MyUserControlBase, new()
{
public MasterWindow() : base(new TMyUserControlBase()) { }
}
Now for the first Window.Show, everything works perfectly, but then I caught two logical errors:
When I close the Window and show a new instance of it with a different UserControl, it loads the content of the first-shown MasterWindow.
When I show a new instance of MasterWindow either with the same UserControl or with a different one without closing the currently-showing window(s), it clears the content of all the currently-showing MasterWindow instances, and loads the content of the first-shown MasterWindow in the new instance.
Note that I can't use the Template property inside the MasterWindowStyle style because the style is actually based on CustomWindow's style (in the real project) which already use the Template property.
Any help will be appreciated.
The reason of observed behavior is you try to set Content of your window in Style. Because it's not a template - WPF will create a tree with your UserControl only once. Then when you apply this style again and again - the same visual tree (with the same, first, UserControl) is reused every time (and of course one control cannot be used in different parents - so it gets removed from where it is hosted now and moved to the window you apply style to).
Long story short - you just should not setting Content via Style in WPF. To fix your immediate problem, you can just set ContentTemplate property instead of Content, and wrap what you have in DataTemplate (leaving everything else the same). This will fix it, because for templates new visual tree is created every time.
Here is yet another way to fix it, still using Content property, however it looks like kind of a hack and I'd better void doing this (though still works):
<Application.Resources>
<Grid x:Shared="False" x:Key="myControl">
<StackPanel>
<TextBlock Text="This is a master window"/>
<ContentPresenter Content="{Binding ContentUserControl, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MasterWindowBase}}}"/>
</StackPanel>
</Grid>
<Style x:Key="MasterWindowStyle" TargetType="{x:Type local:MasterWindowBase}">
<Setter Property="Content">
<Setter.Value>
<StaticResource ResourceKey="myControl" />
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
Here you define your visual tree in resources with x:Shared="False" attribute. This attribute means every time this resource is referenced - new instance will be created (by default - same instance is reused). Then you reference this resource inside your Style.

How to bind a DependencyProperty of a UserControl to a property in it's ViewModel in MVVM?

The question is not about how to get the stuff working, it already does; it's about some strange behavior I'm experiencing, and I need to understand it. I have a ResourceDictionary that contains some styles, one of them got TargetType="{x:Type UserControl}" and x:Key="UCStyle"; that one is applied on multiple UserControls in the project.
Some of these UserControls got string State property in their ViewModel to be used to apply Visual States (through an external class, and an attached property, bound to the ViewModel in XAML). Till this point everything was perfect, then, I tried to add DependencyProperty State to the UserControl, and simply bind it to the state property in the ViewModel, my attempt was:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--ResourceDictionary Source="..."/-->
</ResourceDictionary.MergedDictionaries>
<Style x:Key="MyStyle" TargetType="{x:Type local:MyUserControl}" BasedOn="{StaticResource UCStyle}">
<Setter Property="State" Value="{Binding State, Mode=TwoWay}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Style>
<DynamicResourceExtension ResourceKey="MyStyle" />
</UserControl.Style>
This worked perfectly at the runtime, but in the design-time, it always underline these lines
And shows an error says:
'MyUserControl' TargetType doesn't match type of element 'UserControl'.
And doesn't apply neither UCStyle nor MyStyle in the XAML Viewer in Visual Studio, and doesn't even draw the child UserControls properly. I didn't expect the solution to run properly, but it did!
Now my questions are:
Why does it show these errors in the design-time while it runs properly?
How to get rid of these errors in the design-time? (I cleaned, and re-built the solution, and restarted Visual Studio, and none of these worked)
What's the best practice to deal with `UserControl` Visual States in such situation in MVVM?
What's the best practice to bind a DependencyProperty of a UserControl to a property in it's ViewModel in MVVM?
I'm using Visual Studio 2012.
The wpf designer is nefarious for showing bogus errors at design time. You can't do much but ignore them.
Visual states are a concern of the UI, and therefore should be contained within the UI. MVVM does not mean no codebehind. Use your codebehind for UI tasks, and put your business logic in your view models.
Your question suggests you're creating custom view models to hold view logic for your user controls. Seriously, don't do that. That'll get you in trouble down the road. It interferes with how databinding is designed to work.
There is no "best practice" for binding user control elements to properties defined on its surface. It depends. Using a style to do this seems odd, however. You can simply give the root of the UserControl an x:Name="root" and then use ElementName=root in your binding.
An example of binding within a UserControl to a property defined on the UserControl (taken from an old prototype)...
Here's a UserControl designed to add or delete a list of stuff.
DependencyProperties defined on the UserControl
Bindings within the UserControl that bind to these properties
I don't guarantee this works, but it will illustrate how it's done:
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
It just lists a bunch of things, you can add a new thing or delete a thing from the list. Here's the bindings in xaml
<UserControl x:Class="LolPrototype.ItemsEditor"
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:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
Obviously, you can define DataTemplates outside the list in an ancestor's resources. The point is to show how ElementName bindings can be used to bind against properties defined in the UserControl.

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