XAML How to own StaticResource in derived UserControl class - c#

I have the following problem. I have a class which derives from UserControl, here is the code:
public partial class MyUC : UserControl
{
[...]
public bool IsFlying { get { return true; } }
[...]
}
I want to use a style, which is created for the class MyUC, below is the style code. It is located in App.Xaml :
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dc="clr-namespace:MyNamespace"
<Application.Resources>
<Style x:Key="mystyle" TargetType="dc:MyUC ">
<Style.Triggers>
<Trigger Property="IsFlying" Value="true">
<Setter Property = "Background" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
As you can see I want to use a property which I declared in MyUC.
The problem is that when I am trying to add a style to my control, an error occurres.
<UserControl x:Class="MyNamespace.MyUC"
[...]
Style="{StaticResource mystyle}">
<UserControl.Resources>
</UserControl.Resources>
</UserControl>
The error is: 'MyUC' TargetType does not match type of element 'UserControl'.
As far as I understand, the compiler do not recognize class MyUC to be derived from UserControl. How to fix it?
Thanks in advance!

Error might be at design time only, it should work fine at runtime. Run your app and see if it works for you.
Moreover your trigger won't work for normal CLR property, you need to make it a Dependency Property -
public bool IsFlying
{
get { return (bool)GetValue(IsFlyingProperty); }
set { SetValue(IsFlyingProperty, value); }
}
public static readonly DependencyProperty IsFlyingProperty =
DependencyProperty.Register("IsFlying", typeof(bool),
typeof(SampleUserControl), new UIPropertyMetadata(true));
Also, you can remove the x:Key="mystyle" from your style declaration. It will automatically gets applied to your UserControl.
That way you won't have to explicitly set style on your UserControl. This line won't be required then - Style="{StaticResource mystyle}"

Related

Binding equivalent to an x:Bind in a ControlTemplate?

How can I duplicate the effect of the following binding:
<TextBlock Text="{x:Bind viewmodels:ShellViewModel.TheShellViewModel.CurrentCaptionButtonPathSize, Mode=OneWay}"/>
with a Binding in WinUI 3 (v1.1.5)?
TheShellViewModel is a static ObservableProperty in the ShellViewModel class that refers to the singleton instance of the ShellViewModel class in the application. CurrentCaptionButtonPathSize is an ObservableProperty in that instance.
Using a DataContext or creating a property in the code-behind class enables access with a {Binding ...} but neither of these is available in a ControlTemplate (x:Bind works there if set up correctly).
I thought
<TextBlock Text="{Binding TheShellViewModel.CurrentCaptionButtonPathSize, Source=viewmodels:ShellViewModel}"/>
or something similar would work, but it doesn't. What is the proper form for Path and Source in this case, please?
==== Additional Code (added) ====
public partial class ShellViewModel : ObservableRecipient
{
public static ShellViewModel TheShellViewModel { get; private set; }
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
{
// .ctor code
TheShellViewModel = this;
}
private double _CurrentCaptionButtonPathSize;
public double CurrentCaptionButtonPathSize
{
get => _CurrentCaptionButtonPathSize;
set => SetProperty(ref _CurrentCaptionButtonPathSize, value);
}
}
<ResourceDictionary
xmlns:models="using:PFSI.ViewModels">
<ControlTemplate x:Name="CaptionButtonTemplate" TargetType="Button">
<Border>
<!-- Layout and VisualState infomation -->
<TextBlock x:Name="cbText"
Text="{x:Bind viewmodels:ShellViewModel.TheShellViewModel.CurrentCaptionButtonPathSize, Mode=OneWay}"/>
</Border>
</ControlTemplate>
</ResourceDictionary>
To make the x:Bind on cbText work:
The {x:Bind} markup extension depends on code generation, so it needs a code-behind file containing a constructor that calls InitializeComponent (to initialize the generated code). You re-use the resource dictionary by instantiating its type (so that InitializeComponent is called) instead of referencing its filename.
(see: Data Binding in Depth) which is easily done (not entirely shown, here). Binding in code is straightforward using OnApplyTemplate() on a class derived from Button. Neither of these solutions are as clean and obvious as using some sort of out-of-scope reference for a XAML Binding.
I feel certain I've done this before but, in a Senior moment, I can't remember how. I ultimately went with the OnApplyTemplate() solution in the interests of moving on (and OnApplyTemplate() has some other benefits, too).
Thanks for any advice.
AFAIK, you can't use x:Static in UWP. One way to do this might be accessing the static class via a property.
This works:
MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public ShellViewModel CodeBehindShellViewModel { get => ShellViewModel.TheShellViewModel; }
}
MainWindow.xaml
<Window
x:Class="Miscellaneous.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:viewmodels="using:Miscellaneous.ViewModels"
mc:Ignorable="d">
<StackPanel>
<StackPanel.Resources>
<Style
x:Key="CustomButton"
TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border>
<TextBlock
x:Name="cbText"
Foreground="SkyBlue"
Text="{Binding ElementName=ThisWindow, Path=CodeBehindShellViewModel.CurrentCaptionButtonPathSize}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Style="{StaticResource CustomButton}" />
<Button
Command="{x:Bind viewmodels:ShellViewModel.TheShellViewModel.DoublePathSizeCommand}"
Content="Double" />
</StackPanel>
</Window>
ShellViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Miscellaneous.ViewModels;
public partial class ShellViewModel : ObservableRecipient
{
// You need to instantiate this.
// In this case, I just instantiated it here.
public static ShellViewModel TheShellViewModel { get; private set; } = new();
public ShellViewModel(/*INavigationService navigationService, INavigationViewService navigationViewService*/)
{
// .ctor code
TheShellViewModel = this;
CurrentCaptionButtonPathSize = 123.45;
}
// Assuming you are using the "CommunityToolkit.Mvvm",
// you can use the "ObservableProperty" attribute and
// auto-generate a "CurrentCaptionButtonPathSize" property.
[ObservableProperty]
private double _currentCaptionButtonPathSize;
//public double CurrentCaptionButtonPathSize
//{
// get => _CurrentCaptionButtonPathSize;
// set => SetProperty(ref _CurrentCaptionButtonPathSize, value);
//}
// This is just a test code to check if the "CurrentCaptionButtonPathSize" works.
// The "RelayCommand" will auto-generate a "DoublePathSizeCommand" command for you.
[RelayCommand]
private void DoublePathSize() => CurrentCaptionButtonPathSize *= 2.0;
}

XDG0008 error occuring while implementing DependencyProperty into TemplateBinding [duplicate]

This question already has answers here:
Template Binding with Attached Properties
(2 answers)
Closed 1 year ago.
To easily change the template-specific brushes of a button without directly changing the template, I decided to make a DependencyProperty that will bind to a template-specific brush. That way, I can change this brush just as easy as changing any other regular property. However, after implementing this DependencyProperty, I encountered an error: "Name "ExtensionClass" does not exist in namespace "clr-namespace:extensions"." What causes this error?
XAML:
<ResourceDictionary xmlns:ext="clr-namespace:Extensions"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border" Value="{TemplateBinding Property=ext:ExtensionsClass.MouseOverBackground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
C#:
namespace Extensions {
public class ExtensionsClass {
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register("MouseOverBackground", typeof(Brush), typeof(Button));
public static void SetMouseOverBackground(UIElement element, Brush value) {
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element) {
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
}
In addition to the problem with the Binding, which is covered in the answer to the duplicate question, you also have to be aware that you are declaring an attached property, which has to be registered with the RegisterAttached method.
Besides that, in both the Register and the RegisterAttached methods, the third argument has to be the type that declares the property, not the type of element where you intend to set the property, i.e. typeof(ExtensionsClass) here.
public static class ExtensionsClass
{
public static readonly DependencyProperty MouseOverBackgroundProperty =
DependencyProperty.RegisterAttached(
"MouseOverBackground",
typeof(Brush),
typeof(ExtensionsClass),
null);
public static void SetMouseOverBackground(UIElement element, Brush value)
{
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element)
{
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
You bind to an attached property by means of a Binding Path with parentheses:
<Setter
Property="Background"
TargetName="border"
Value="{Binding Path=(ext:ExtensionsClass.MouseOverBackground),
RelativeSource={RelativeSource TemplatedParent}}"/>

How to change the roundness of a button in a generic way

I am currently working on making my WPF application a little bit more generic.
Up to this point, for each button I wanted to create, I used a different style to modify roundness (and it creates a lot of useless code).
Using the following code I've managed to create a variable I can change from the XAML file, but I cannot link it to the roundness itself.
Could anyone please tell me what I am doing wrong? I already have checked on so many forums but no one seems to have the answer other than "don't do it in a generical way".
I can precise that everything is compiling and the style is otherwise correctly applied to the button (there is no xaml linking problem).
The style I am using:
<Style x:Key="AwakeButton" TargetType="{x:Type customcontrols:AwakeButton}" BasedOn="{StaticResource {x:Type Button}}"
xmlns:extensions="Awake.Services.Properties:Extensions">
<Setter Property="customcontrols:AwakeButton.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding Path=BorderRoundness}" />
<!--<Setter Property="CornerRadius" Value="10" />-->
</Style>
</Style.Resources>
</Style>
The overload of the button I created to do so:
public class AwakeButton : Button
{
public AwakeButton()
{
}
public static DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(AwakeButton));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
How I am using it in the page:
<customcontrols:AwakeButton Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" BorderRoundness="5">
You have to bind the BorderRoundness to the parent AwakeButton, otherwise it is resolved using the current DataContext, which does not contain this property. Furthermore, if you derive from Button, you do not have to make the dependency property attached, you could just register a normal one using the Register(...) method. Also make DPs static and readonly.
<Setter Property="CornerRadius" Value="{Binding BorderRoundness, RelativeSource={RelativeSource AncestorType={x:Type local:AwakeButton}}}" />
If you do not change anything special about the button, you could also create attached properties instead of a dedicated sub type just for exposing a BorderRoundness property.
public static class ButtonProperties
{
public static readonly DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(ButtonProperties));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
You can refer to the BorderRoundness using attached property binding syntax (parentheses).
<Style x:Key="AwakeButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="local:ButtonProperties.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding (local:ButtonProperties.BorderRoundness), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</Style>
</Style.Resources>
</Style>
You use regular button now with the newly created attached border roundness property.
<Button Grid.Row="0" Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" local:ButtonProperties.BorderRoundness="5"/>
The roundness is applied as CornerRadius to the Border of the Button. The Border is defined in the ControlTemplate of the Button. The ControlTemplate defines the appearance of a control.
In other words, you need to delegate the property values to the related elements in the ControlTemplate.
To delegate the values to the ControlTemplate, you have to override this template and bind the templated parents properties to the template elements:
In your AwakeButton define the BorderRoundness property as simple DependencyProperty (not attached) and the override the default style definition, so that the AwakeButton will use its own default Style. This way the Button is reusable withgout having to redefine the Style each time you weant to use it, which is especially important wjen you publish your project as library:
AwakeButton.cs
public class AwakeButton : Button
{
public static readonly DependencyProperty BorderRoundnessProperty = DependencyProperty.Register(
"BorderRoundness",
typeof(Thickness),
typeof(AwakeButton),
new PropertyMetadata(default(Thickness)));
public Thickness DestinationPath
{
get => (Thickness) GetValue(AwakeButton.BorderRoundnessProperty);
set => SetValue(AwakeButton.BorderRoundnessProperty, value);
}
static AwakeButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AwakeButton), new FrameworkPropertyMetadata(typeof(AwakeButton)));
}
}
Generic.xaml.cs
This file is located in the Themes folder and contains all default styles. WPF will automatically check this file for a default style and apply it if no other Style override was found.
<Style TargetType="AwakeButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="AwakeButton">
<Border BorderBrush={TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding BorderRoundness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Stayle>
Example
<Grid>
<AwakeButton BorderRoundness="8" />
</Grid>
But if you want to make it really general, using an attached property, you have to make a attached behavior. The following code works with every DependencyObject that contains a Border as child in its visual tree:
class Element : DependencyObject
{
#region CornerRoundness attached property
public static readonly DependencyProperty CornerRoundnessProperty = DependencyProperty.RegisterAttached(
"CornerRoundness",
typeof(CornerRadius),
typeof(Element),
new PropertyMetadata(default(CornerRadius), Element.OnCornerRoundnessChanged));
public static void SetCornerRoundness(DependencyObject attachingElement, CornerRadius value) =>
attachingElement.SetValue(Element.CornerRoundnessProperty, value);
public static CornerRadius GetCornerRoundness(DependencyObject attachingElement) =>
(CornerRadius) attachingElement.GetValue(Element.CornerRoundnessProperty);
#endregion CornerRoundness attached property
private static void OnCornerRoundnessChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (Element.TryFindVisualChildElement(attachingElement, out Border elementBorder))
{
elementBorder.CornerRadius = (CornerRadius) e.NewValue;
}
}
public static bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (Element.TryFindVisualChildElement(childElement, out resultElement))
{
return true;
}
}
return false;
}
}
Example
<StackPanel>
<Button Element.CornerRoundness="8" />
<ToggleButton Element.CornerRoundness="8" />
</StackPanel>

Control as dependencyproperty / WPF

I have my custom control derived from Control class. I want to create dependency property of another control (for example, button) and place it in ControlTemplate (so button can be placed in xaml and MyControl's users can subscribe to it's events etc.). May someone tell me, how can I do it?
Here is result code example:
public class MyControl: Control
{
static MyControl( )
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
public static readonly DependencyProperty MyButtonProperty =
DependencyProperty.Register("MyButton",
typeof(Button),
typeof(MyControl),
new PropertyMetadata(default(Button)));
public Button MyButton
{
get
{
return (Button) GetValue(MyButtonProperty);
}
set
{
SetValue(MyButtonProperty, value);
}
}
}
xaml:
<ControlTemplate TargetType="{x:Type lib:MyControl}">
<Canvas>
<Border Child="{TemplateBinding MyButton}">
</Border>
</Canvas>
</ControlTemplate>
Your control's template can declare a dependency on child controls via the TemplatePartAttribute. You then get an instance of that dependency in your OnApplyTemplate method.
[TemplatePart(Name = PartButton, Type = typeof(ButtonBase))]
public class MyControl : Control
{
private const string PartButton = "PART_Button";
private ButtonBase buttonPart;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.buttonPart = GetTemplateChild(PartButton) as ButtonBase;
}
}
Your control template would then look something like:
<Style TargetType="MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MyControl">
<Border ...>
<Button x:Name="PART_Button" .../>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style
Note that this.buttonPart could be null if the template did not include an appropriately named ButtonBase within it. You should strive to ensure your control still works when template parts are missing.

Updating Custom Attached Property in Style Trigger with Setter

I was trying out attached properties and style triggers hoping to learn more about it.
I wrote a very simple WPF windows app with an attached property:
public static readonly DependencyProperty SomethingProperty =
DependencyProperty.RegisterAttached(
"Something",
typeof(int),
typeof(Window1),
new UIPropertyMetadata(0));
public int GetSomethingProperty(DependencyObject d)
{
return (int)d.GetValue(SomethingProperty);
}
public void SetSomethingProperty(DependencyObject d, int value)
{
d.SetValue(SomethingProperty, value);
}
And I was trying to update the 'Something' attached property with a property trigger defined in the button style section:
<Window x:Class="TestStyleTrigger.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestStyleTrigger;assembly=TestStyleTrigger"
Title="Window1" Height="210" Width="190">
<Window.Resources>
<Style x:Key="buttonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="local:Window1.Something" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Button Style="{StaticResource buttonStyle}"></Button>
</Window>
However, I kept getting following compilation error:
error MC4003: Cannot resolve the Style Property 'Something'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property. Line 10 Position 29.
I can't understand why it gives me this error because I did use the 'Class.Property' syntax in the tag of the section. Can any one tell me how can I fix this compilation error?
Your backing methods for the dependency property are named incorrectly and must be static:
public static int GetSomething(DependencyObject d)
{
return (int)d.GetValue(SomethingProperty);
}
public static void SetSomething(DependencyObject d, int value)
{
d.SetValue(SomethingProperty, value);
}
Also, you shouldn't specify the assembly in the local XML NS mapping in the XAML because the namespace is in the current assembly. Do this instead:
xmlns:local="clr-namespace:TestStyleTrigger"

Categories