WPF Custom Control Property Setter - wpf

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>

Related

sidebar with materialIcons

I have a custom control, that I mad, for hosting the name, and icon of the panel, and made triggers, so it will change colors when I hover the icon or select it
public class NavButton : ListBoxItem {
static NavButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavButton),
new FrameworkPropertyMetadata(typeof(NavButton)));
}
public Uri NavLink {
get { return (Uri)GetValue(NavLinkProperty); }
set { SetValue(NavLinkProperty, value); }
}
// This property will hold specifics on where to navigate within our appreciation
public static readonly DependencyProperty NavLinkProperty =
DependencyProperty.Register(
"NavLink", typeof(Uri),
typeof(NavButton),
new PropertyMetadata(null));
// This property will hold specifics on the icon
public Label Icon {
get { return (Label)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(Label),
typeof(NavButton),
new PropertyMetadata(null));
The problem is, that whenever I call this control in my main, I always get this error
I am calling it like this
<custom:NavButton
Padding="6"
FontFamily="{StaticResource Material}"
Icon="{x:Static fonts:IconFont.VolumeHigh}"/>
And this is the recourse dictionary for the label
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<FontFamily x:Key="Material">/Fonts/Material.ttf#Material Design Icons</FontFamily>
</ResourceDictionary>
<Style TargetType="{x:Type local:NavButton}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NavButton}">
<Border
x:Name="back"
Padding="{TemplateBinding Padding}"
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8">
<Label
x:Name="icon"
Content="{TemplateBinding Icon}"
FontFamily="{StaticResource Material}"
FontSize="48"
Foreground="#7b8793" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="back" Property="Background" Value="#d0ebff" />
<Setter TargetName="icon" Property="Foreground" Value="#2a84f1" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="back" Property="Background" Value="#d0ebff" />
<Setter TargetName="icon" Property="Foreground" Value="#2a84f1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
error message clearly says, that you have a type mismatch. Icon DP should not have type Label. it should have the same type as IconFont.VolumeHigh. or it could simple be object:
// This property will hold specifics on the icon
public object Icon {
get { return (Label)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(object),
typeof(NavButton),
new PropertyMetadata(null));

Type key for style not found

I created a custom user control called FlatButton deriving from Button:
XAML:
<Button
x:Class="Program.GUI.Controls.FlatButton"
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:GUIControls="clr-namespace:Program.GUI.Controls"
mc:Ignorable="d">
<Button.Resources>
<ResourceDictionary>
<Style x:Key="{x:Type GUIControls:FlatButton}" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource ResourceKey={x:Type Button}}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GUIControls:FlatButton}">
<Grid x:Name="LayoutRoot" Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}">
<TextBlock
x:Name="Text"
Text="{TemplateBinding Content}"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverBackground}"/>
<Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverForeground}"/>
</Trigger>
<Trigger Property="IsMouseCaptured" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickBackground}"/>
<Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickForeground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Button.Resources>
</Button>
CS:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Program.GUI.Controls {
/// <summary>
/// Interaction logic for FlatButton.xaml
/// </summary>
public partial class FlatButton : Button {
public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register(
"HoverBackground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty HoverForegroundProperty = DependencyProperty.Register(
"HoverForeground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty ClickBackgroundProperty = DependencyProperty.Register(
"ClickBackground",
typeof(Brush),
typeof(FlatButton)
);
public static readonly DependencyProperty ClickForegroundProperty = DependencyProperty.Register(
"ClickForeground",
typeof(Brush),
typeof(FlatButton)
);
public Brush HoverBackground { get; set; }
public Brush HoverForeground { get; set; }
public Brush ClickBackground { get; set; }
public Brush ClickForeground { get; set; }
static FlatButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatButton), new FrameworkPropertyMetadata(typeof(FlatButton)));
}
public FlatButton() {
this.InitializeComponent();
}
}
}
The Problem is when I try to create an extending style the base style is not found:
MainWindow XAML:
<Style x:Key="DefaultDarkButtonThemeStyle" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource {x:Type GUIControls:FlatButton}}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource ColorTextPrimaryDark}"/>
<Setter Property="HoverBackground" Value="{DynamicResource ColorPrimaryDarkLight}"/>
<Setter Property="HoverForeground" Value="{DynamicResource ColorTextPrimaryDarkLight}"/>
<Setter Property="ClickBackground" Value="{DynamicResource ColorPrimaryDarkLightLight}"/>
<Setter Property="ClickForeground" Value="{DynamicResource ColorTextPrimaryDarkLightLight}"/>
</Style>
Exception:
System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '45' and line position '47'.'
Inner Exception
Exception: Cannot find resource named 'Program.GUI.Controls.FlatButton'. Resource names are case sensitive.
If you develop a custom control like FlatButton, you should create a separate style that either resides in a theme resource dictionary (e.g. Themes/Generic.xaml) if you develop a custom control library or any other dedicated resource dictionary. Regardless of which path you choose, you can merge the corresponding resource dictionary into the application resources or any other resource dictionary on a lower scope in order to make the style accessible within that scope (apply it automatically if it is implicit or creating styles based on it, whatever).
Your partial XAML definition is the usual approach for creating UserControls, but not for custom controls. The style for FlatButton in the resources of the Button markup can only be resolved within the scope of this local resource dictionary, which means the FlatButton itself or down its visual tree. Look at the static resource lookup behavior to understand how the style is being resovled and why it fails.
The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.
The lookup process then traverses the logical tree upward to the parent element and its resource dictionary. This process continues until the root element is reached.
App resources are checked. App resources are those resources within the resource dictionary that is defined by the Application object for your WPF app.
Consequently, when your DefaultDarkButtonThemeStyle is created in your main window and tries to resolve the GUIControls:FlatButton base style, it does not find it, because it is not defined within the window resources or anywhere up the visual tree or the application resources.
However, if you simply created a theme or regular resource dictionary that contained the style and included it in the window or application resource, it would be resolved successfully.
For more information on creating custom controls, including how to set up styles and resource dictionaries, you can refer to Control authoring overview.

Trigger for custom dependency properties in Style

The problem
I defined a reusable control, MyControl, that extends a TextBox.
I want to set a Trigger to one of its dependency properties.
So I added a style to it, with Triggers.
But if I set the TargetType of the Style to MyControl, I get a XAML warning that 'MyControl' TargetType does not match type of element 'TextBlock'.
And if I set it to TextBlock, I get a compilation error that The member "MyDependencyProperty" is not recognized or is not accessible..
How can I define this style with the triggers?
Sample
C# code-behind
namespace UserControls.Local
{
public partial class MyControl : TextBlock
{
#region Trogdor
public static readonly DependencyProperty TrogdorProperty = DependencyProperty.Register(
"Trogdor", typeof (bool), typeof (MyControl), new PropertyMetadata(default(bool)));
public bool Trogdor
{
get { return (bool) GetValue(TrogdorProperty); }
set { SetValue(TrogdorProperty, value); }
}
#endregion
public MyControl()
{
InitializeComponent();
}
}
}
XAML
<TextBlock
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:local="clr-namespace:UserControls.Local"
mc:Ignorable="d"
Text="BOOP!"
x:Class="UserControls.Local.MyControl">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Blue"/>
<Style.Triggers>
<Trigger Property="Trogdor" Value="True">
<Setter Property="Foreground" Value="DeepPink"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The solution I found was to "fully qualify" the dependency property on the binding:
<Trigger Property="local:MyControl.Trogdor" Value="True">
Not sure if you are still looking for a solution, but the answer in this thread worked for me, while yours didn't.
It uses a DataTrigger with a binding on the root element, instead of a Trigger:
<DataTrigger Binding="{Binding Path=Highlight, RelativeSource={RelativeSource AncestorType={x:Type Elements:DataElement}}}" Value="True">
<Setter Property="Control.Background" Value="{DynamicResource EntryBoxHighlightBackground}"/>
</DataTrigger>
With your solution, I press the button and the value of the variable changes but the style trigger doesn't apply the changes, like it was not informed of the change in the variable.

Show popup with binding on DataGrid row mouse over

When a user hovers a row in a DataGrid I want to show a popup with some information about this row.
I've stuck how to bind DataTrigger to every row in dynamically populated DataGrid table.
I've found solutions only for tooltips but tooltips don't suit me because I need to have more control on popup (don't hide it immediately when a user moved a mouse cursor to another control, ability to click in popup with mouse etc.)
Here is XAML code where I'm trying to bind popup DataTrigger to every DataGrid row (I've put comments with questions in the code below)
<Window x:Class="VKPN.UI.Windows.TestWindow"
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:VKPN.UI.Windows"
mc:Ignorable="d"
Title="TestWindow" SizeToContent="WidthAndHeight">
<Grid>
<Popup Name="UserPopup" Placement="RelativePoint" HorizontalOffset="-5" VerticalOffset="0"
PlacementTarget="{Binding ElementName=ThisUserControl}">
<Popup.Style>
<Style TargetType="Popup">
<Style.Triggers>
<!--How to specify binding to every DataGridTable row below?-->
<DataTrigger Binding="{Binding ElementName=DataGridTable, Path=???}" Property="IsMouseOver" Value="True">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<Label>
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<!--How to specify binding to every DataGridTable row below?-->
<DataTrigger Binding="{Binding ElementName=???}" Property="IsMouseOver" Value="True">
<!--DataGrid row has a column "id" which I want to show in the label. Did I do it correct below?-->
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=DataContext.id}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Popup>
<DataGrid Name="DataGridTable" ItemsSource="{Binding}" IsReadOnly="True" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
</DataGrid>
</Grid>
</Window>
Please help me to figure out how to do it.
Create 2 AttachedProperty called RowPopup and ShowPopup.
RowPopup will hold a reference to Popup control, and ShowPopup will show/hide this Popup based on DataGridRow.IsMouseOver property. These are extremely easy to implement.
Create a Style with TargetType DataGridRow.
Example,
xmlns:local="clr-namespace:MyNamespace"
<Style TargetType="DataGridRow" x:Key="RowStyleKey">
<Setter Property="local:CustomADP.RowPopup" Value="{Binding ElementName=Popup1}"/>
<Setter Property="local:CustomADP.ShowPopup" Value="{Binding IsMouseOver, Mode=OneWay, RelativeSource={RelativeSource Self}}"/>
</Style>
<DataGrid RowStyle="{StaticResource RowStyleKey}" ... />
AttachedProperties Code :
public class CustomADP
{
/********* Set Popup to show ****************/
public static Popup GetRowPopup(DependencyObject obj)
{
return (Popup)obj.GetValue(RowPopupProperty);
}
public static void SetRowPopup(DependencyObject obj, Popup value)
{
obj.SetValue(RowPopupProperty, value);
}
public static readonly DependencyProperty RowPopupProperty =
DependencyProperty.RegisterAttached("RowPopup", typeof(Popup), typeof(CustomADP), new PropertyMetadata(null));
/************* Show Hide using IsOpen property ************/
public static bool GetShowPopup(DependencyObject obj)
{
return (bool) obj.GetValue(ShowPopupProperty);
}
public static void SetShowPopup(DependencyObject obj, bool value)
{
obj.SetValue(ShowPopupProperty, value);
}
// Using a DependencyProperty as the backing store for ShowPopup. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowPopupProperty =
DependencyProperty.RegisterAttached("ShowPopup", typeof(bool), typeof(CustomADP), new PropertyMetadata(false, new PropertyChangedCallback(ShowPopupCallback)));
private static void ShowPopupCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is DataGridRow))
return;
if (((DataGridRow)d).IsFocused == true)
{
Popup p = CustomADP.GetRowPopup(d);
p.IsOpen = Convert.ToBoolean(e.NewValue);
}
else
{
Popup p = CustomADP.GetRowPopup(d);
p.IsOpen = Convert.ToBoolean(e.NewValue);
}
}
}

How to override base style control template in derived style in 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>

Resources