Place style inside Custom Control - wpf

I'm creating the custom control. And suddenly faced a question: why to put control's style separately from the control?
I mean that using the standard way you must: derive from base control (for example, from TextBox) and add a style for it in general.xaml.
But why can't we do just like this:
<TextBox x:Class="CustomTest.CoolTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox.Style>
<Style>
<Setter Property="TextBox.FontSize" Value="20" />
</Style>
</TextBox.Style>
</TextBox>
And code-behind:
public partial class CoolTextBox : TextBox
{
public CoolTextBox()
{
InitializeComponent();
}
}
Update #1
I'm not writing my own library but creating a custom control inside my main executable. Application support themes but they differ only by colors. So each theme is a set of brush resources and my style will refer them using DynamicResource.
What I want to know is the drawbacks of that solution. I mean performance, simplicity of usage and etc.

WPF allows changing themes at runtime, means the style shall be stored separatly from the controls. Futhermore the control shall be lookless when designining in order to other programmers to have their custom styles though somewhere there should be a default style which must be stored separatly in a Generic.xaml file. If your app doesn't support changing themes then you can define the style wherever you wish.
If you are writing a library of custom controls I suggest you to stick to standards.
Here is a link how shall a custom controls be created:
http://wpftutorial.net/HowToCreateACustomControl.html

In addition to dev hedgehog's answer about performance, I found the following drawbacks:
Style object is own for each instance of control. So you end up with number of clones of the same Style object.
You cannot override style using BasedOn property. Only completely replace is possible.

Related

Setting implicit style of custom control in WPF

My custom control's default style is defined in Generic.xaml and is working fine.
However, when I try to change the style of my custom control the same way as one can with built in controls nothing happens. In App.xaml I am trying to change the default style of my control by doing the following:
<Style TargetType="{x:Type my:CustomControl}">
<Setter Property="Background" Value="Red"/>
</Style>
If I set the x:key property of the above style and reference this style using this key all works fine.
Is it correct that the above styling method only works for built in controls and does not work for custom controls, or am I just doing something wrong? Is there a workable solution to achieve this type of styling for custom controls?
Update
In this situation my custom control is derived from System.Windows.Window.
I finally managed to get implicit styling for my custom control to work. Apparently implicit styling might not work for derived controls as the style is not automatically being applied to the control. In order to achieve this one has to manually set the resource reference. My custom control now looks like this:
public class CustomControl : Window
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public CustomControl()
{
SetResourceReference(StyleProperty, typeof(CustomControl));
}
}
Yes, you are correct. Generic.xaml is used for custom controls, and App.xaml for application-wide resources (including styles for built-in controls). Specifying TargetType for a custom control in App.xaml will not work. So using explicit styles (with x:Key) seems to be the easiest solution.

When overriding DefaultStyleKey is necessary?

I have just spent like 2 hours trying to figure out why after moving a control from solution A to solution B (along with its style), the control stops showing up (control template was not applied). Turned out I forgot to override default style key. Here is the question: why did it work for solution A?
In DesignerView.cs:
public class DesignerView : Control {
// No DefaultStyleKeyProperty.OverrideMetadata here
}
In Generic.xaml:
<Style TargetType="{x:Type controls:DesignerView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:DesignerView}">
<TextBlock Text="Hello" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Of course, my style is a little bit more complicated than that, but anyhow: exactly the same control (class+style, no proper DefaultStyleKeyProperty set) did show up in solution A, but didn't in solution B.
I guess you are talking about this:
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl),
new FrameworkPropertyMetadata(typeof(MyControl)));
}
Every control needs theme style which define its default template. (Most often for custom controls its defined under Themes\Generic.xaml).
DefaultStyleKeyProperty defines the key used to find the theme style of the control. If you comment out this line, it will pick default template of base class which generally is of type Control. (But since Control does not have any default template defined for it, your control is not shown when you comment out this line)
So, defaultStyleKeyProperty metadata needs to be overriden in static constructor to indicate that its default style is declared under Themes\Generic.xaml.
If you change base class to say Button and you comment out this line, you will see it pick default template of Button class.
So, for your question if your custom control is deriving from
Control, you need to override it to provide default template of control. In case deriving from control
whose default template is already defined then you can avoid
overriding it. It will pick base control style.
That being said for your question
why did it work for solution A?
I suspect you have defined an explicit style somewhere in your
solution A which is missing from Solution B. And Solution B doesn't
have theme style set as well because of no override of metadata.

WPF altering property on just PART_EditableTextBox of a combBox

I would like to make a change to the PART_EditableTextBox of the default wpf combobox (change the background for example).
I tried adding a style like this:
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<TextBox x:Name="PART_EditableTextBox" Background="AntiqueWhite"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But that causes the rest of the default template to be ignored.
Is there a way I can just override a specific property of the PART_EditableTextBox or do I have to copy over the entire control template and make my change in there?
Some different ways to change the look of the Control...
Copying a Controls Template, Editing it and Using it in a Style
When copying and modify the Template of a control...you have to bear in mind 1 thing...themes.
The control might have completely different Template designs depending on the theme (i.e. different chrome)...and so your problem is...which template do you choose to copy, modify and then use on your control.
No matter, which one you choose...you have a problem...when someone is running Windows in a theme that is different to the theme from which you copied the Template from...well that control will look wrong/out of place.
To see how different Templates can look like in different themes...use ShowMeTheTemplate:
http://www.sellsbrothers.com/posts/details/2091
So to do it properly, you would have to copy and modify the template for each theme (Classic, Luna, Aero, Royale, etc) and do the necessary steps to get your different themed template to get loaded when the theme changes...so that your control is "theme aware".
When overriding WPF templates do I have to override each theme's template separately?
http://windowsclient.net/blogs/nidonocu/archive/2008/02/16/wpf-themes-and-control-libraries.aspx
http://blogs.windowsclient.net/nidonocu/archive/2008/03/03/wpf-themes-and-control-libraries-part-2.aspx
Modifying the Visual tree After Template has been applied at runtime
If you were the author of the control or you create a derived version of a control...then you can wait till the template is applied and then in OnApplyTemplate...you can then hunt for the "Part" in the visual tree, and then modify the visual tree/changes attributes at runtime (i.e. you could change the Background of PART_EditableTextBox).
However, this doesn't work if you are relying on implicit styles (as you are), or don't want to or can't replace all your controls with the derived version in your XAML.
Obtaining a Copy of the ControlTemplate at runtime, and modifying the "Part"
There is another possibility....obtaining the ControlTemplate for a control at runtime...(which will be for the current theme set at the time)....modifying it, then setting it onto the Control.
The beauty of this is if there are new themes in the system for which you didn't have prior knowledge of their names (and thus didn't design a Template for it), then instead of your control Template being picked up from Generic theme (and thus being out of place)....you have a better chance for the look to fit in better with the new theme. But it is a bit of a hack.
https://siderite.dev/blog/cloning-wpf-controltemplate.html
Define a WPF ControlTemplate at runtime
Using 'BasedOn' will only overwrite the properties you specify in your new style.
However, in your case, the property you are overwriting is the Template. This is the entire template for the combobox, because that is the property you are trying to modify in your style.
To overwrite just part of it, you have to copy over the entire control template and make your change there; just like you thought.
If you don't have Expression Blend to retrieve the entire control template, you can find them on MSDN.

Implicit styles in Application.Resources vs Window.Resources?

I was looking at this question and noticed that placing an implicit TextBlock style in Application.Resources applies that style to all TextBlocks, even those inside other controls such as Buttons, ComboBoxes, etc
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Blue" />
</Style>
</Application.Resources>
Placing the implicit style in Window.Resources does not cross control template boundaries, so things like Buttons and ComboBoxes maintain their default black text.
<Window.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Blue" />
</Style>
</Window.Resources>
Furthermore, adding the default style in the Application.Resources makes it so you can't overwrite that style with another implicit style.
<!-- Doesn't work if implicit style with same property is in Application.Resources -->
<ComboBox.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
</Style>
</ComboBox.Resources>
My questions are:
Why is this?
Are there other differences between Application.Resources and Windows.Resources?
When should use one over the other?
I understand that Application.Resources apply to the entire application, while Window.Resources apply to the window only, however I want to know why the styles in Application are treated differently than styles in Window
This is really the only special handling added to WPF and it was done by design. The code that implements it can be found in FrameworkElement, in the method FindImplicitStyleResource, which effectively does:
internal static object FindImplicitStyleResource(FrameworkElement fe, object resourceKey, out object source)
{
// ...
DependencyObject boundaryElement = null;
if (!(fe is Control))
boundaryElement = fe.TemplatedParent;
// ...
}
So the rule of thumb is that implicit Styles are always applied to controls (i.e. derives from Control). Assuming the implicit Style can be found.
For elements used in a ControlTemplate that do not derive from Control, such as TextBlock, the implicit Style look up will not cross it's templated parent. In your case above, this would be the ComboBox.
I believe this was done so that non-application implicit Styles for TextBlock were not inadvertently applied to TextBlock elements used in control templates, which the developer may or may not have known were there. The implicit Styles would only be applied to TextBlocks actually created by the developer in their own XAML.
The application implicit Styles would still allow global styling, such as increasing font size. But has probably caused more confusion than it's worth.
There is no good answer to say when to use one versus the other, as they each have their function. Obviously if you don't want to affect every TextBlock in your application, you shouldn't put the style in the application resources.
But keep in mind that this affects any non-Control element, such as Shape elements.
Pretty plain as simple
If you want Resources to be shared among the ENTIRE application you would use Application.Resources
If you want Resources to be shared among the ENTIRE Window you would use Window.Resources
If you want Resources to be shared among a single control you would use (Whatever Control).Resources
Lets say you have multiple windows but you only want a default style in one but not the other then you would use Windoe.Resources
Rachel, I don't think there is anything special to "Styles". Moreover, there isn't an issue of "crossing template boundaries". The reason for this is different and it goes to the different "Trees" in a WPF applicaiton.
By your question I recon you are picturing a world with the following hierarchy:
- Application => Window => Control => Elements within the control
There is no such hierarchy. There are different trees in a WPF applicaiton, the most famous are the Logical Tree and the Visual Tree, but there are more (the routing event tree and also the resource lookup tree, with slightly different semantics).
Assume the following XAML:
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="btn" Click="click">Click Me</Button>
</Grid>
</Window>
For this XAML, the logical tree will look like:
- Window => Grid => Button => String
The textblock inside the button is not part of the logical tree (it is part of the VisualTree though).
Looking up for resources goes by the LogicalTree, with one difference. After it reaches the top object, the finding resource algorithm will look at the Application resource dictionary, and then at the Theme resource diectionary, and then at the System resource dictionary in this order.
See following articles:
About trees: http://msdn.microsoft.com/en-us/library/ms753391.aspx
About finding resources: http://msdn.microsoft.com/en-us/library/ms750613.aspx#staticdynamic, and look for the section 'Dynamic Resource Lookup Behaviour'
Finnaly, to prove my point, add the following resource to the applicaiton XAML:
<Application x:Class="SO.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
StartupUri="MainWindow.xaml">
<Application.Resources>
<clr:String x:Key="MyResource">Hello Application Resource</clr:String>
</Application.Resources>
</Application>
and the following code behind:
private void click(object sender, RoutedEventArgs e)
{
// Logical Children of btn
Debug.WriteLine("Children of btn:");
foreach( var x in LogicalTreeHelper.GetChildren(btn) ) {
Debug.WriteLine("{0} : {1}", x, x.GetType());
}
// Walk the visual tree
Debug.WriteLine("The Visual Tree:");
WalkVisual(0, this);
// Find the textblock within the button
DependencyObject p = btn;
while (p.GetType() != typeof(TextBlock))
p = VisualTreeHelper.GetChild(p, 0);
TextBlock tb = p as TextBlock;
// Now climp the textblock through the logical tree
while (p != null)
{
Debug.WriteLine("{0}", p.GetType());
p = LogicalTreeHelper.GetParent(p);
}
// Find a resource for the textbox
string s = tb.FindResource("MyResource") as string;
Debug.WriteLine("MyResource Content: {0}", s);
}
private void WalkVisual(int indent, DependencyObject p)
{
string fmt = string.Format("{{0,{0}}}{{1}}", indent * 4);
Debug.WriteLine(fmt, "", p.GetType());
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(p); ++i)
{
WalkVisual(indent+1,VisualTreeHelper.GetChild(p, i));
}
}
So ... once you understand the first question ('why is that'), the other questions fall apart. The different between application resources and window resources is that application resources can be fount by any DependencyObject in your application, including those defined in other assemblies. You'll use it when this is what you want to acheive :-)
u.
The difference lies in the scope of the styles :
when placed in Application.Resources, a style will apply to all controls in the application
when placed inside Windows.Resources, a style will apply to all controls in the window
the difference is quite subtle there, but what it means is that whatever control we're talking about (included one that's in another control's template) will get the style from application.Resources. but only the controls directly children of the window will get the style from window.Resources. A control inside antoher control's template will not have the style defined in Window.Resources since it is not directly in the window, whereas it will have the style defined in Application.Resources since it is in the application.
as for your second point, it has to do with dependency property precedence I think:
http://msdn.microsoft.com/en-us/library/ms743230.aspx
CodeNaked Answered true. It is made by WPF design. I opened the ticket question here which is a full explanation that it is by design. Also, what are online documents (dependency-property-precedence-list), function
(DependencyPropertyHelper.GetValueSource) and desktop tools (snoopwpf) to use to help you navigate through these behaviors.
While we are unhappy as WPF users (we expect the same behavior) we can't do anything about it. Options: Explicitly define content like <Label.Content> or quotation " to do considerably more work - custom templates, custom type for the content, new control etc. which I don't see as really necessary."

Getting started with a derived custom control?

I am experimenting with derived custom controls, and I created what I thought would be the simplest possible derivation:
I created a custom control project in VS 2010 and changed the base class for CustomControl1 from Control to Calendar.
Then I went into Generic.xaml and removed the default Style created for CustomControl1.
Finally I created a WPF app to consume the control.
When I add the custom control to the app's MainWindow, I had expected to see a regular WPF calendar, since I had derived from Calendar and made no changes to the Calendar control templates.
Instead, nothing shows up at design time or run time. MainWindow remains empty. I am not sure what is going on, but it is pretty obvious that I have made a faulty assumption somewhere along the line.
Can anyone clear this up for me? Thanks for your help.
BTW--why am I doing this? I am extending the Calendar control, but I will only need to modify the CalendarDayButton control template. Before I get to my modifications, I figure I should be able to display the unmodified Calendar first. Like I said, I think I'm making a faulty assumption somewhere.
CustomControl1.cs
Here is the code for CustomControl1:
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class CustomControl1 : Calendar
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
Generic.xaml
Here is the markup for Generic.xaml, which is located in the control's Themes folder:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
</ResourceDictionary>
MainWindow
Finally, here is the MainWindow.xaml markup:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1" Title="MainWindow" Height="350" Width="525">
<Grid>
<WpfCustomControlLibrary1:CustomControl1 />
</Grid>
</Window>
WpfApplication1 contains a reference to the WpfCustomControlLibrary1 project.
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
->What this line says is that CustomControl1 has its default style defined in Generic.xaml
Then I went into Generic.xaml and removed the default Style created for CustomControl1.
-> What this does is remove the default style for CustomControl1
So your control has no style so it shows nothing :D
Rather than removing the style from generic.xaml, you should copy the the style of the Calender control and change TargetType to CustomControl1 or create a new style and add BasedOn Calender
Edit to add a little more info to David's answer below for people having a look down the road
<Style TargetType="{x:Type local:FsCalendar}" BasedOn={x:Type Calender}>
<Setter Property="CalendarDayButtonStyle" Value="{StaticResource FsCalendarDayButtonStyle}" />
</Style>
This is all you need in the style. BasedOn will take care of copying everything from the default style and it will also take care of different themes. If you copy the style from the default theme of calender you will break the look for all the themes except for the one from which you copied the 'default' style.
I found my answer--thanks to NVM for all the help! This applies to controls generally, but it applies particularly to the Calendar control. If you are going to modify only part of the control, you don't have to include all of the constituent control templates.
But you do have to include the main control template, which you point to your custom control, and you have to establish a chain from the main control to the template you want to modify. In the case of my Calendar control, I need to modify only the CalendarDayButton template to implement the changes I want to make. So, here is what I did:
I included the main Calendar template, and point that toward my custom control.
Then, to get down to the CalendarDayButton, I added a property setter to point my main Calendar style's CalendarDayButtonStyle property to my custom CalendarDayButton style.
Here is what the main Calendar style declaration in my Generic.xaml file ends up looking like:
<!-- Calendar Style -->
<Style TargetType="{x:Type local:FsCalendar}">
<Setter Property="CalendarDayButtonStyle" Value="{StaticResource FsCalendarDayButtonStyle}" />
...
</Style>
The remainder of the main Calendar style is unchanged--it is a copy of the default style.
BTW, the CalendarDayButton style definition must appear before the main Calendar style definition in Generic.xaml, or the main Calendar style won't be able to find it.
I have written a Code Project Article titled Extending the WPF Calendar Control. It walks through the step involved in extending a complex control like the WPF Calendar. Hopefully, it will help others who are grappling with the same issues.
BTW, I have since discovered the Style.BasedOn property, which will let you derive a style from an existing style without having to repeat the base style. There is a good blog post on it here.

Resources