WPF: User Control access Style via Dependency Property - c#

I have created a Style for a Button with an Image:
<Style x:Key="StyleButtonBase" TargetType="Button">
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Sizes/Sizes.xaml" />
<ResourceDictionary Source="../Colors/Brushes.xaml" />
<ResourceDictionary Source="../Fonts/Fonts.xaml" />
<ResourceDictionary Source="../Images/Images.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
<Setter Property="Background" Value="{StaticResource BrushButtonActive}" />
<Setter Property="Foreground" Value="{StaticResource BrushForegroundLight}" />
<Setter Property="FontFamily" Value="{StaticResource FontFamilyDefault}" />
<Setter Property="FontSize" Value="{StaticResource DoubleFontSizeStandard}" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{StaticResource BrushBorder}"
BorderThickness="{StaticResource ThicknessBorder}"
CornerRadius="{StaticResource CornerRadius}">
<Image Source="{StaticResource IconIcon}" Stretch="None" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource BrushButtonPressed}" />
</Trigger>
</Style.Triggers>
</Style>
Now I Want to create a User-Control which only consists of a Button with this style and a Dependency Property to set the Button Image. The XAML part of my user control looks like this:
<UserControl
x:Class="HH.HMI.ToolSuite.ResourceLib.Controls.ButtonSmall">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Buttons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Button Style="{StaticResource StyleButtonBase}" Width="{StaticResource DoubleWidthButtonSmall}" Height="{StaticResource DoubleHeightControls}">
</Button></UserControl>
The code behind of my user-control looks like this:
public partial class ButtonSmall : UserControl, INotifyPropertyChanged
{
public ButtonSmall()
{
InitializeComponent();
}
public static readonly DependencyProperty ButtonImageProperty
= DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(TextOutput), new PropertyMetadata(null, OnButtonImagePropertyChanged));
private static void OnButtonImagePropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
ButtonSmall temp = dependencyObject as ButtonSmall;
temp.OnPropertyChanged("ButtonImage");
temp.OnButtonImagePropertyChanged(e);
}
private void OnButtonImagePropertyChanged(DependencyPropertyChangedEventArgs e)
{
ButtonSmallImage.Source = ButtonImageSource;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ImageSource ButtonImageSource
{
get { return (ImageSource)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
}
In my other user-controls i usually access an element in the user control itself like:
xamlname.text = text
Now i haven't a named element in my xaml code of the user-control. Instead i have the named element in the style, which i reference in the user control. How can access this throug my code behind?

If I were you I'd subclass Button and create a new class (just a .cs file) like so:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MyProject
{
public class IconButton : Button
{
public static readonly DependencyProperty ButtonImageProperty = DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(IconButton),
new FrameworkPropertyMetadata(new BitmapImage(), FrameworkPropertyMetadataOptions.AffectsRender));
public ImageSource ButtonImage
{
get { return (ImageSource)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
}
}
This means you can now reference the property. Otherwise, because your button is just a regular button (with only a regular button's properties; no image), your style doesn't know about your new image property which it expects a Button to have. Don't forget to update your style's TargetType's to point to IconButton.
If you place your style in the resources section of your User Control, you can set the button style like so:
<UserControl x:Class="MyProject.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myclass="clr-namespace:MyProject">
<UserControl.Resources>
<!-- your style here -->
</UserControl.Resources>
<myclass:IconButton Style="{StaticResource StyleButtonBase}/>
</UserControl>
(The xmlns 'myclass' must be replaced to refer to the namespace your custom button is in!)
Also, if you remove the x:Key property from the style, it will apply to all buttons in scope, meaning you can omit setting it explicitly. This may be handy if you locate it in a shared ResourceDictionary (if you're building a library of custom controls for example) (if you do this, you will need to combine this resource dictionary in your App.xaml.cs file). If you end up doing that and you discover your UserControl doesn't have any especial functionality beyond wrapping an IconButton, you can of course omit it entirely and just use IconButtons directly in other controls. Your style declares how your IconButton looks, and your IconButton class ensures that the resources (your image) your style expects are there when it looks for them at runtime, so as long as your style is in scope, you're good to go.

If the Style is defined in Application.Resources in App.xaml, or in a resource dictionary that's merged into Application.Resources in App.xaml, you can just reference it in the user control via StaticResource. If it's in another resource dictionary, you'll have to merge that one into UserControl.Resources.
Or you can put it directly in UserControl.Resources as TernaryTopiary suggests, if it won't be needed elsewhere.
As for the image source property, you could write a Button subclass as Ternary suggests, or you could write an attached property (see below). In XAML, when you customize controls, first you try to do it with regular attributes; then you try to restyle the thing. Then you escalate to replacing the control template, and that won't quite do the job, you consider attached properties/behaviors. Only if all else fails to do you resort to subclassing. You should know how to do it, but you should also learn the other ways of doing things.
In this case, there's a somewhat quick and dirty way to do it that's consistent with the correct XAML way of doing things: The Content property of the button is going unused, and its declared type is Object, so we can just use that. Since we're using a binding to pass in the image source, you can get rid of that PropertyChanged handler on ButtonImageSource.
<UserControl
...>
<!-- ... -->
<Button
Content="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{StaticResource StyleButtonBase}"
Width="{StaticResource DoubleWidthButtonSmall}"
Height="{StaticResource DoubleHeightControls}"
/>
And make the following change in the control template in StyleButtonBase:
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{StaticResource BrushBorder}"
BorderThickness="{StaticResource ThicknessBorder}"
CornerRadius="{StaticResource CornerRadius}"
>
<!-- Now we'll find the image source in the button's Content --->
<Image
Source="{TemplateBinding Content}"
Stretch="None"
/>
</Border>
</ControlTemplate>
Attached Property
Using Content for this is a very mild abuse of WPF: You shouldn't really be repurposing properties. Content is universally understood in WPF to mean "any arbitrary content", not "any ImageSource".
So it's a little more "correct" to use an attached property, and it's not a lot more work. Here's how that would look.
We'll define the attached property in a separate static class, because I can't think of a good name for it other than ButtonImageSource, which you're already using in SmallButton:
public static class ButtonHelper
{
#region ButtonHelper.ButtonImageSource Attached Property
public static ImageSource GetButtonImageSource(Button obj)
{
return (ImageSource)obj.GetValue(ButtonImageSourceProperty);
}
public static void SetButtonImageSource(Button obj, ImageSource value)
{
obj.SetValue(ButtonImageSourceProperty, value);
}
public static readonly DependencyProperty ButtonImageSourceProperty =
DependencyProperty.RegisterAttached("ButtonImageSource", typeof(ImageSource), typeof(ButtonHelper),
new PropertyMetadata(null));
#endregion ButtonHelper.ButtonImageSource Attached Property
}
In the XAML, the user control uses this attached property instead of Content:
<Button
Style="{StaticResource StyleButtonBase}"
local:ButtonHelper.ButtonImageSource="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
And the control template does likewise:
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4"
>
<Image
Source="{TemplateBinding local:ButtonHelper.ButtonImageSource}"
Stretch="None"
/>
</Border>
</ControlTemplate>
All the attached property does is give us a property that isn't named Content, and which is strongly typed as ImageSource, which we can use to pass in that image source.
Another thing: Maybe this was an error that crept in when you simplified your code for the question, but you're passing typeof(TextOutput) to DependencyProperty.Register() where you should be passing typeof(ButtonSmall). More importantly, you've got two names for what should be a single property: ButtonImage and ButtonImageSource.
public static readonly DependencyProperty ButtonImageSourceProperty
= DependencyProperty.Register(
"ButtonImageSource",
typeof(ImageSource),
// Should be ButtonSmall, not TextOutput
typeof(ButtonSmall),
new PropertyMetadata(null));
public ImageSource ButtonImageSource
{
get { return (ImageSource)GetValue(ButtonImageSourceProperty); }
set { SetValue(ButtonImageSourceProperty, value); }
}
Incidentally, in your Style, it would be better practice to use TemplateBinding for BorderBrush and BorderThickness, and set the defaults in Style setters, the way you did with Background.

Related

Wpf - how to create a library of custom control to use and customize in differente application

I am creating a collection of custom controls in a project MyLibrary.UI. What I want to achieve is to define the some properties in a component that can be customize in every main app that uses MyLibrary.UI.
I wanto to make an example of customizing an Icon in the control FilteredComboBox.
I tried two ways:
I added a DependencyProperty FindImage defined in FilteredComboBox.cs:
public class FilteredComboBox : ComboBox
{
...
#region FindImageProperty
public static readonly DependencyProperty FindImageProperty = DependencyProperty.Register(
nameof(FindImage), typeof(BitmapImage),
typeof(FilteredComboBox),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
DefaultValue = new BitmapImage(new Uri("pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"))
});
public BitmapImage FindImage
{
get
{
return (BitmapImage)GetValue(FindImageProperty);
}
set
{
SetValue(FindImageProperty, value);
}
}
#endregion FindImage
static FilteredComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FilteredComboBox), new FrameworkPropertyMetadata(typeof(FilteredComboBox)));
}
...
}
and modified the style FilteredComboBoxStyle.xaml as below:
<ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
...
<DockPanel>
<Image Source="{TemplateBinding FindImage}" Width="25" Height="25" DockPanel.Dock="Left"/>
<TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
</DockPanel>
...
</ControlTemplate>
<Style TargetType="{x:Type local:FilteredComboBox}" x:Key="baseFilteredCBStyle">
...
<Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
</Style>
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}"/>
Then I added the reference of this control style in Themes/generic.xaml and I defined in the resources of App.xaml of my application the following style:
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
</Setter.Value>
</Setter>
</Style>
I would expect that this would change the icon with MyCustomApp.Icons, but it still keeps the Icon in MyLibrary.Icons.
Then I tried to use a DynamicResource to set the image, so in FilteredComboBoxStyle.xaml:
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"/>
<ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
...
<DockPanel>
<Image Source="{DynamicResource myImage}" Width="25" Height="25" DockPanel.Dock="Left"/>
<TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
</DockPanel>
...
</ControlTemplate>
<Style TargetType="{x:Type local:FilteredComboBox}">
...
<Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
</Style>
Then in the resources of App.xaml:
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
With this approach my application shows the icon from MyCustomApp.Icons.
My questions are:
Why the option 1 is not working? There is something wrong with it?
Is the option 2 the proper way to customize the custom controls to be application-specific?
I made an example with an Image but could be any property of a control.
The Themes/generic.xaml resource dictionary should be located in the project where the custom control is defined.
If you want to set the FindImage property using an implicit style in the consuming application, you should put the Style in App.xaml or in a resource dictionary that is merged into App.xaml:
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...>
<Application.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
Your question is very difficult because it is full of typos and compiler errors. It's hard to tell where you did wrong and where you were just sloppy when creating the example. Maybe you should create the question more carefully next time.
Option 1 is not working because you can't reference a resource defined in Generic.xaml from your application. Generic.xaml is meant as a theme dictionary for theme resources, like the default control Style of FileteredComboBox.
This means you can't base a Style defined in App.xaml on an explicitly named resource that is defined in Generic.xaml.
Option 2 is working because there is no such an illegal reference to a resource that is defined in Generic.xaml. The Style in Generic.xaml references a resource (the BitmapImage) using the DynamicResource markup. The lookup behavior of this markup (opposed to StaticResource) occurs at runtime: once the application is running, lookup starts from the location of the referencing element and traverses up the logical tree to visit every ResourceDictionary along the route. Then the XAML engine checks the App.xaml and finally Generic.xaml for the requested resource.
In your particular case the lookup starts directly at App.xaml, where the XAML engine successfully finds the BitmapImage with the matching key.
To fix your issue, you must base the Style defined within the application scope on the target type (which will implicitly translate to the default Style of the styled control):
BasedOn="{StaticResource {x:Type local:FilteredComboBox}}"
App.xaml
<Style TargetType="{x:Type local:FilteredComboBox}"
BasedOn="{StaticResource {x:Type local:FilteredComboBox}}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage x:Key="myImage"
Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png" />
</Setter.Value>
</Setter>
</Style>
I have to point out that it is not necessary to base your Style on the default Style explicitly.
The default Style is always loaded first. Any additional Style that targets the same control will be applied commulative i.e it will be "merged" into the default style: unless FrameworkElement.OverridesDefaultStyle is set to true, only the duplicate property setters are overwritten. Otherwise the default Style will be completely ignored (overridden).
In other words, application scope styles are always implicitly based on the control's default Style.

'Failed to create a 'Type' from the text 'local:ImageButton'

I have a class assembly that contains a number of user controls. I want to add a new type of button to this class assembly and I also want to add styling to it. Specifically I want a button that contains multiple images for use in Normal, Hover (IsMouseOver) and disabled states. It will also contain text to display. Then I could add this in any application using this class assembly as follows:
<ns:ImageTextButton NormalImage="{StaticResource SomeImage}" HoverImage="{StaticResource SomeHoverImage}" Text={StaticResource SomeText}" />
First I created a C# class:
public class ImageTextButton : Button {
public DependencyProperty ImageProperty = DependencyProperty.Register(nameof(Image), typeof(DrawingBrush), typeof(ImageTextButton));
public DependencyProperty HoverImageProperty = DependencyProperty.Register(nameof(HoverImage), typeof(DrawingBrush), typeof(ImageTextButton));
public DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ImageTextButton));
public DrawingBrush Image {
get { return GetValue(ImageProperty) as DrawingBrush; }
set { SetValue(ImageProperty, value); }
}
public DrawingBrush HoverImage {
get { return GetValue(HoverImageProperty) as DrawingBrush; }
set { SetValue(HoverImageProperty, value); }
}
public string Text {
get { return GetValue(TextProperty) as string; }
set { SetValue(TextProperty, value); }
}
}
Then I created a style in a Styles.xaml file which is compiled as a Resource.
<Style TargetType="{x:Type local:ImageTextButton}" x:Key="ImageTextButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0" Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}">
<StackPanel>
<Canvas x:Name="canvas" Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}" />
<TextBlock Style="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=TextStyle}"
Text="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Text}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=HoverImage}" TargetName="canvas" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have not been able to test this style and there very well may be issues in it. I can't get the application to start because the TargetType above doesn't exist. I believe this is because my Styles.xaml is a resource and the actual class is compiled. The error I get is Failed to create a 'Type' from the text 'local:ImageTextButton'.
How can I achieve this? Secondly, is there any way to apply this style by default to this type? I don't want to always have to specify Style={StaticResource ImageTextButtonStyle} in each instance of this user item.
First you have to decide if you are going to build an UserControl or a CustomControl.
UserControl needs to be derived from "UserControl" Base class. As your C# code extends "Button", that implementation doesn't fall under "UserControl".
For a CustomControl, your Style should be present in a file named "Generic.xaml" which SHOULD BE PLACED in a folder "Themes". (You can still change the default Themes Folder location). And your C# file should define the Key which will be used to find/target/identify the Xaml Style.
Your above setup should not work as it satisfies neither Usercontrol nor Custom Control Requirements.
I cannot create a full example but can direct you to a certain extent.
You need to have the below Static Method to set your default style as a bare minimum. Add remaining properties as required.
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageButton()
{
}
For the Xaml Part. Create a Folder Themes and inside that create a resource dictionary by name "Generic.xaml". Inside your Generic.Xaml, add your styles. (Place your xaml logic inside the control template).
<Style TargetType="{x:Type bc:ImageButton}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type bc:ImageButton}">
</ControlTemplate>
</Setter>
</Style>
All the above are bare minimum requirement for a CUSTOM CONTROL. In case you wish to create a UserControl, you need a .xaml & a .xaml.cs files. You can easily use visual studio context menu (rightclick on solution) and create a usercontrol.
Important: When you are creating a custom control, you dont need something like below
Background="{Binding RelativeSource={RelativeSource AncestorType=local:ImageTextButton}, Path=Image}">
Just use, TemplateBinding
Background="{TemplateBinding Image}">
The RelativeSource Mode FindAncestor is missing
change {RelativeSource AncestorType=local:ImageTextButton} to {RelativeSource FindAncestor, AncestorType=local:ImageTextButton}

Setting a style to wpf custom control overrides everything

I am creating a split button where I am trying to set a default template to it so if the split button is to be used elsewhere outside of the control, it can be. The issue here is, is that when a user calls the split button into their control and they attach their style to it, it completely removes everything from the split button. I'm not entirely sure how to fix it. I would appreciate any help.
MySplitButton.xaml:
<local:SplitButton x:Class="WpfApp4.SplitButton.MySplitButton"
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:WpfApp4.SplitButton"
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="100">
<local:SplitButton.Resources>
</local:SplitButton.Resources>
<local:SplitButton.Style>
<Style TargetType="{x:Type local:SplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:SplitButton.Style>
</local:SplitButton>
MySplitButton.xaml.cs
public partial class MySplitButton : SplitButton
{
public MySplitButton()
{
InitializeComponent();
}
}
public class SplitButton : ToggleButton
{
public ICommand PrimaryButtonCommand
{
get { return (ICommand)GetValue(PrimaryButtonCommandProperty); }
set { SetValue(PrimaryButtonCommandProperty, value); }
}
public static readonly DependencyProperty PrimaryButtonCommandProperty;
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty;
public bool ContextMenuOpen
{
get { return (bool)GetValue(ContextMenuOpenProperty); }
set { SetValue(ContextMenuOpenProperty, value); }
}
public static readonly DependencyProperty ContextMenuOpenProperty;
static SplitButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton)));
PrimaryButtonCommandProperty = DependencyProperty.Register("PrimaryButtonCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null));
ToggleLockProperty = DependencyProperty.Register("ToggleLock", typeof(bool), typeof(SplitButton), new UIPropertyMetadata(false));
ContextMenuOpenProperty = DependencyProperty.Register("ContextMenuOpen", typeof(bool), typeof(SplitButton), new FrameworkPropertyMetadata(false));
}
}
public class LockableToggleButton : ToggleButton
{
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty =
DependencyProperty.Register("ToggleLock", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
protected override void OnToggle()
{
if (!ToggleLock)
{
base.OnToggle();
}
}
}
So when I call MySplitButton on MainWindow like this, and attach a style to it, everything gets overridden and I don't know what I am doing wrong:
<Window x:Class="WpfApp4.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:WpfApp4"
xmlns:cc="clr-namespace:WpfApp4.SplitButton"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.PrimaryButtonUICommand}" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>
<Grid>
<cc:MySplitButton x:Name="SplitButton" Margin="346,197,345,188" Style="{DynamicResource thisStyle}">
<StackPanel Orientation="Horizontal">
<Label Content="hello"/>
</StackPanel>
</cc:MySplitButton>
</Grid>
</Window>
The way that you created the XAML markup for your custom button, the style will get instantiated and applied to a MySplitButton instance when it is created. Specifying a TargetType on a style does not automatically inherit the default style. You can base a style on another using the BasedOn attribute. However, you cannot reference your default style, since it is only available on an instance of MySplitButton. The solution is to extract the default style to a resource dictionary to share it.
Usually, when creating custom controls, you would create a dedicated assembly and create a resource dictionary called Generic.xaml in a Themes folder. This resource dictionary contains your default control styles. Note that the TargetType is MySplitButton, because that is your custom control, not SplitButton. Since there is no x:Key, this style is implicit and will be applied to all MySplitButton controls in scope automatically.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControlLibrary">
<!-- Default style for your split button -->
<Style TargetType="{x:Type local:MySplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MySplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ...styles, templates and resources for other controls. -->
</ResourceDictionary>
In other projects you have to include the resources in the application resources, or at least in any resource dictionary in a scope where you use the controls. Otherwise, the style cannot be resolved.
<Application x:Class="MyApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MyControlLibrary;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Specifying a TargetType does not automatically inherit a style of a control.
Gets or sets the type for which this style is intended.
In order to base one style on another, you have to specify the base style through the BasedOn attribute.
Gets or sets a defined style that is the basis of the current style. [...] When you use this property, the new style will inherit the values of the original style that are not explicitly redefined in the new style.
Consequently, you have to adapt your new thisStyle like below.
<Style x:Key="thisStyle" TargetType="{x:Type cc:MySplitButton}" BasedOn="{StaticResource {x:Type cc:MySplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
Remember, your original SplitButton style must be available in the current scope, so your users must make sure to include the corresponding resource dictionary in their library or application.
Try to use BasedOn.
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}" BasedOn="{StaticResource {x:Type cc:SplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>

Attached property always has a default value in a style

I'm working with WPF and I want to use attached properties to work with some styling things in the validation of the controls (my example of the problem is really simple, binding a simple text).
This is my attached property:
public class ToolTipExtension
{
public static readonly DependencyProperty ShowToolTipProperty = DependencyProperty.RegisterAttached(
"ShowToolTip", typeof(string), typeof(ToolTipExtension), new PropertyMetadata("Deffault"));
public static void SetShowToolTip(DependencyObject element, string value)
{
element.SetValue(ShowToolTipProperty, value);
}
public static string GetShowToolTip(DependencyObject element)
{
return (string) element.GetValue(ShowToolTipProperty);
}
}
I have a simple style dictionary like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:at="clr-namespace:CarpetaTecnicaWPF.AttachedProperties"
>
<Style TargetType="{x:Type TextBox}" x:Key="Blah" >
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(at:ToolTipExtension.ShowToolTip)}" FontSize="50"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
As you can see, I'm trying to bind the Text property to my attached property.
In my Page, I'm using the style like this:
<TextBox Style="{StaticResource Blah}" at:ToolTipExtension.ShowToolTip="Prueba?"/>
The thing is, the value Prueba? does not appear. When I inspect the tree, I see this:
But in runtime, the result of the binding is Deffault
What am I doing wrong?
Your binding is incorrect.
The TemplatedParent in this case is not what you actually need. The ControlTemplate for the error is not applied to the text box itself, it's a stand-alone control template. So you are just getting a default value from a wrong FrameworkElement.
To access the text box your error template is applied to, you need to use the AdornedElementPlaceholder in your ControlTemplate. From that AdornedElementPlaceholder, you can access your text box via the AdornedElement property.
Here is an example:
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<AdornedElementPlaceholder x:Name="adorner"/>
<TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(at:ToolTipExtension.ShowToolTip)}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>

Elements of list that is bound in a ControlTemplate only rendered once

I have a control that is wrapping an Xceed DataGridControl (part of the Extended WPF Toolkit Community Edition). The control provides a simple property (without a backing dependency property) that can hold a list of buttons (field instantiated by the constructor):
public List<Button> GroupButtons
{
get { return groupButtons; }
set { groupButtons = value; }
}
The items of the property are then added in the XAML of a view that is using the control:
<local:CustomControl ...>
<local:CustomControl.GroupButtons>
<Button>foo<Button>
</local:CustomControl.GroupButtons>
...
</local:CustomControl ...>
I would like to render the buttons of this list inside the so-called "GroupHeaderControl" of the Xceed Datagrid, which is basically a grouping row like shown below:
To achieve this, I've overwritten the ControlTemplate of the GroupHeaderControl:
<ResourceDictionary ...>
<Style TargetType="{x:Type controls:CustomControl}">
<Style.Resources>
<Style TargetType="{x:Type xcdg:GroupHeaderControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xcdg:GroupHeaderControl}">
<Border ...>
<StackPanel Height="{TemplateBinding Height}" Orientation="Horizontal">
<ContentPresenter />
<ItemsControl ItemsSource="{Binding GroupButtons, RelativeSource={RelativeSource AncestorType={x:Type controls:CustomControl}}}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
</Style>
...
</ResourceDictionary>
Now here comes the problem: Instead of rendering the button(s) for each instance of the GroupHeaderControl, it is rendered only once. For illustration, imagine that in the image above only the button at the second group header ("Lyon") is visible while the other one ("Reims") is not.
The problem is apparently related to the fact that the items of the GroupButtons list are added via the XAML definition. If I hard code the items of the list, it works like a charm:
public List<Button> ButtonList
{
get { return new List<Button>()
{
new Button() { Content = "foo" }
}
}
I don't really get where this behavior is coming from. Does somebody have an idea?

Categories

Resources