Setting a style to wpf custom control overrides everything - c#

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>

Related

How to "inject" a different ControlTemplate in a CustomControl?

I have a WPF project for a CustomControl.
It has a .cs with some Dependency Properties:
namespace CustomControlLib
{
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public Style IcStyle
{
get { return (Style)GetValue(IcStyleProperty); }
set { SetValue(IcStyleProperty, value); }
}
public static readonly DependencyProperty IcStyleProperty =
DependencyProperty.Register("IcStyle", typeof(Style), typeof(CustomControl1));
.....
and XAML code for two ControlTemplates in its Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ic="clr-namespace:CustomControlLib">
<Style x:Key="ListBoxInputControl" TargetType="{x:Type ic:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ic:CustomControl1}">
<Grid x:Name="ListRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Labels"/>
<ColumnDefinition Width="320"/>
</Grid.ColumnDefinitions>
<Label
x:Name="PART_NameLabel2"
Grid.Column="0"
Margin="1">
<Label.Content>
...
</Label.Content>
</Label>
<ListBox
Grid.Column="1"
ItemsSource="{TemplateBinding ...}"
SelectedIndex="{TemplateBinding ...}"
SelectedItem="{TemplateBinding ...}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboBoxInputControl" TargetType="{x:Type ic:CustomControl1}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ic:CustomControl1}">
<Grid
x:Name="ComboRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Labels"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
<Label.Content>
...
</Label.Content>
</Label>
<ComboBox
x:Name="PART_ComboBox"
Grid.Column="1"
DisplayMemberPath="{TemplateBinding ...}"
ItemsSource="{TemplateBinding ...}"
SelectedItem="{TemplateBinding ...}">
</ComboBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
(and there is a third one, for a TextBox)
I want to use the CustomControl like this:
<Window
x:Class="CoCa.Views.MainWindow"
...
xmlns:ic="clr-namespace:CustomControlLib;assembly=CustomControlLib">
...
<ic:CustomControl1
Name1="PLF"
IcSelectedIndex="{Binding Path=.....}"
IcItemsSource="{Binding Source=...}"
IcStyle="{StaticResource ic:ListBoxInputControl}"/>
<ic:CustomControl1
Name1="PLF"
IcSelectedIndex="{Binding Path=.....}"
IcItemsSource="{Binding Source=...}"
IcStyle="{StaticResource ic:ComboBoxInputControl}"/>
I was hoping to be able to plug the right ControlTemplate in the CustomControl1 like this.
But alas: it does not work like this.
I get the message: "The resource "ic:ComboBoxInputControl" could not be resolved."
How would it work?
Try to reference the Style using its x:Key:
IcStyle="{StaticResource ComboBoxInputControl}"/>
You also need to merge the ResourceDictionary where the styles are defined into the scope of the Window, e.g.:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CustomControlLib;component/themes/generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>

Cannot add content to a Custom Control

I'm trying to create a custom version of UserControl to implement some standard animations for views when loading.
But when I add a ContentPresenter I'm not able to add content to my control. Why though?
Here is the Template I use for my custom control.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EzNintendo.Desktop.Controls">
<Style TargetType="{x:Type local:AnimatedView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AnimatedView}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
and the default code for a custom control
public class AnimatedView : Control
{
static AnimatedView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedView),
new FrameworkPropertyMetadata(typeof(AnimatedView)));
}
}
and
and that is how I try to use it.
<controls:AnimatedView x:Class="MyView"
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:controls="clr-namespace:MControls;assembly=MControls">
<Grid>
<TextBlock Text="Hello World!" />
</Grid>
</controls:AnimatedView>
When I remove the Grid it works just fine.

xaml resourcedictionary content setter foreground not displaying correctly

I am attempting to make a ResourceDictionary of some standardised colour schemes etc to add to a dll class library for use in future applications. I am new to XAML and seem to have made an error when it comes to creating or using the content setter part of the dictionary. I cannot set and use the colour of the text in the dictionary. Here is what I have so far; As you can see, the Foreground of the TextBlock in the Content is set to White.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My_Class_Library.WPF_Resources">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../WPF Resources/Buttons.xaml"/>
<ResourceDictionary Source="../WPF Resources/Brushes.xaml"/>
<ResourceDictionary Source="../WPF Resources/Sliders.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="myButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button" >
<Grid>
<Rectangle Name="ClickFill" Fill="ForestGreen" RadiusX="5" RadiusY="5"/>
<ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content">
<Setter.Value>
<Grid>
<TextBlock Background="{x:Null}" Foreground="White"></TextBlock>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
and here's the reference to the dictionary:
<Grid x:Class="My_Class_Library.Update_Information"
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:My_Class_Library"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../WPF Resources/Buttons.xaml"/>
<ResourceDictionary Source="../WPF Resources/Brushes.xaml"/>
<ResourceDictionary Source="../WPF Resources/Sliders.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Button Name="Click"
Width="100" Height="30"
Style="{StaticResource myButton}"
Click="Click_Click">
_click
</Button>
</Grid>
however, what I see is this:
, which as you can see has black (presumably default) text instead of the specified white. What am I doing wrong, that causes the content not to be set?
I know everybody hates questions like "look at this, what's wrong with it?" but I am at my wit's end trying to find a solution - I am following loads of training videos and so on and the above is my best effort... Nearly everything else I try breaks the whole thing! Any pointers very appreciated!
You've kind of got the right idea but you'll want to get used to setting properties from the style template as setter directly. I'd also suggest you start with a default style template of a control and edit it to your needs since like for instance in this case you're going to lose all of the additional Visual states for things like MouseOver etc.
However for the sake of your immediate question, you're going to ditch your Content property all together from the template (that's your ContentPresenter job) and instead just do this (in pseudo);
<Style x:Key="myButton" TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<!-- Rest of it goes here -->
</Style>
...and voila. Hope this helps, cheers!

WPF: User Control access Style via Dependency Property

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.

Set WPF UserControl properties to a Control inside it [duplicate]

I know how to create a custom user control in WPF but how can I make it so that someone can provide an ItemTemplate?
I have a user control that is a mixture of several other WPF controls, one of them being a ListBox. I'd like to let the user of the control specify the content of the list box but I'm not sure how to pass that information through.
EDIT: The accepted answer works with the following correction:
<UserControl x:Class="WpfApplication6.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication6">
<ListBox ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:MyControl}}, Path=ItemsSource}" />
</UserControl>
You will want to add a DependencyProperty to your control. The xaml will look slightly different if you are deriving from UserControl or Control.
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(MyControl), new UIPropertyMetadata(null));
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
}
Here is xaml for a UserControl.
<UserControl x:Class="WpfApplication6.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication6">
<ListBox ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:MyControl}}}" />
</UserControl>
Here is xaml for a Control:
<Style TargetType="{x:Type src:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type src:MyControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListBox ItemTemplate="{TemplateBinding ItemTemplate}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories

Resources