Say I have defined a button with rounded corners.
<Style x:Key="RoundButton" TargetType="Button">
<!-- bla bla -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="0,5,5,0" />
<!-- bla bla -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I it possible that the user of this button can specify the CornerRadius? Can I use a TemplateBinding? But where should I bind to? (to Tag?)
In addition to Kent's suggestions, you could also create an attached property to define the CornerRadius on the button, and bind to that property in the template
In order to use a TemplateBinding, there must be a property on the templated control (Button, in this case). Button does not have a CornerRadius or equivalent property, so your options are:
hard code the value in the template
Hijack another property (such as Tag) to store this information. This is quicker, but lacks type safety, is harder to maintain, and prevents other uses of that property.
Subclass Button and add the propery you need, then provide a template for that subclass. This takes a little longer but yields a much nicer experience for consumers of your control.
The button type doesn't have a property for CornerRadius, so templating this won't be possible. I think the easiest way is creating a new class which inherits from Button and add a new dependency property for the CornerRadius. Like this:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication3
{
public class RoundedButton:Button
{
public CornerRadius CornerRadius
{
get { return (CornerRadius) GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof (CornerRadius),
typeof (RoundedButton), new UIPropertyMetadata());
}
}
In xaml you can use it like:
<Local:RoundedButton
Style="{DynamicResource RoundButton}"
Width="64" Height="32"
Content="Hello"
CornerRadius="1,5,10,5"
Background="#FF9CFFD5" />
A template binding to the CornerRadius will work without a problem now.
Related
I want some of my Button, ToggleButton and RadioButton to have a Geometry property so that I can use in ControlTemplates to avoid boilerplate when assigning instance-specific Geometries to those controls.
For example, currently I can write this:
<my:GeometryButton Geometry="{StaticResource OneGeometry}"/>
<my:GeometryButton Geometry="{StaticResource OtherGeometry}"/>
<!-- ...and inside the Style for GeometryButton: -->
<ContentControl TargetType="{x:Type my:GeometryButton}">
<Border>
<Path Data={TemplateBinding Geometry}/>
</Border>
</ContentControl>
With this GeometryButton class:
public class GeometryButton : Button
{
static GeometryButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GeometryButton),
new FrameworkPropertyMetadata(typeof(GeometryButton)));
}
public Geometry Geometry
{
get { return (Geometry)GetValue(GeometryProperty); }
set { SetValue(GeometryProperty, value); }
}
public static readonly DependencyProperty GeometryProperty =
DependencyProperty.Register("Geometry",
typeof(Geometry),
typeof(GeometryButton),
new PropertyMetadata(default(Geometry)));
}
The problem is, if I am to define GeometryToggleButton and GeometryRadioButton classes, I am supposed to repeat the DependencyProperty code in each class, violating DRY.
Also, since RadioButton derives from ToggleButton, and the later and Button in turn derive from ButtonBase, I think I could take advantage of this, but if I need to inherit from each class separately, I don't benefit from inheritance at all.
So I considered to use AttachedProperties, but the tutorials and examples usually mention examples like DockPanel.Dock, Grid.Left, or Control.Foreground, implying the existence of some "Parent", so I am not sure of:
Does the AttachedProperties concept applies to my use case in the first place?
If yes, how am I supposed to implement it?
Create a regular attached property. In your control templates, use it.
No seriously, there's not a lot more to it than that.
For example, I wrote an attached CornerRadius property so that many different control styles could specify a CornerRadius, which would be used by their templates.
public static class Attached
{
public static readonly DependencyProperty CornerRadiusProperty
= DependencyProperty.RegisterAttached(
"CornerRadius",
typeof(CornerRadius),
typeof(Attached),
new FrameworkPropertyMetadata(
new CornerRadius(0),
FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
PropertyChanged)
);
public static CornerRadius GetCornerRadius(DependencyObject uie)
{
return (CornerRadius)uie.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject uie, CornerRadius value)
{
uie.SetValue(CornerRadiusProperty, value);
}
}
XAML
N.B. The parens around (edcorpext:Attached.CornerRadius) in the Binding are critical, so it understands that the string is one indivisible path segment referring to an attached property; otherwise it tries to parse it as a path to a property of Binding.Source, hits the :, and throws an exception.
<ControlTemplate x:Key="EdCorpButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<Border
x:Name="PART_BackgroundBorder"
CornerRadius="{Binding (edcorpext:Attached.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1.3"
BorderBrush="{StaticResource ControlBorderBrush}"
Background="{StaticResource EdCorpGrayMediumGradientBrush}"
SnapsToDevicePixels="True"
/>
<!-- etc. etc. etc. -->
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="edcorpext:Attached.CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
<Setter Property="Template" Value="{StaticResource EdCorpButtonTemplate}" />
<!-- etc. etc. etc. -->
They told me we needed the UI on this application to look "more modern" and since we don't have a real designer who knows what he's doing, I put asymmetrical rounded corners on stuff. It was actually a lot worse before.
I'd like to understand which properties of an xaml Control are applied to the ControlTemplate of that Control. F.e. If I create a Control based on the Window Class like this:
(This is very simplified — It doesn't make sense in the current state I know...)
public class BaseWindow : Window {
public BaseWindow() { }
}
And the Template:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
xmlns:local="clr-namespace:Arctic">
<Style TargetType="{x:Type local:BaseWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BaseWindow}">
<Grid Background="Transparent">
<Border Background="{TemplateBinding Background}"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Now, when I specify a BaseWindow Control in my app the Margin Property is applied to the BaseWindow without specifying a TemplateBinding. The Background isn't, I have to declare the TemplateBinding in the Template in order to achieve that. Can you explain to me why some properties are applied to the ControlTemplate by default and others are not?
My guess is, that the Window.xaml (Default Window Template of WPF) binds to some properties like the Margin but ignores some like Background. If that is true, then I do not understand why I can set the Background in a Window Control and it is applied to it. Seems like the Window binds to some properties and stops doing that when you derive from it…
This is probably completely wrong — I just wanted to explain my thoughts.
Window class inherit FrameworkElement and all its properties including FrameworkElement.Margin. Same goes for Control.Background. Your question is why you have to do something to have Control.Background working.
Answer is simple:
Margin is used in layouting, its functionality is implemented/provided by FrameworkElement and it happens always, invisible for you and disregarding of ControlTemplate (because all framework elements participate in layouting and use margin).
Background, in turn, is provided to be use by visuals. It's up to you how to use it, because only you know how control will looks like. Control doesn't know what to do with that property.
So, you have to use TemplateBinding to bind Background to some color in your ControlTemplate, but Margin works without need to do anything in control template.
This is a bit of a weird question:
I have a custom control that inherits from TextBox, and provides "ghost" text - eg it says "Username" in a box until you click inside it, whereupon the "ghost" text disappears, and the user can type in their, in this case, Username.
The "Ghost text" for a control is simply a property in a subclass of TextBox. I then set TextBox.Text to it whenever relevant.
In the Visual Studio WPF XAML preview window (the standard UI design one), I would like to be able to "preview" the "Ghost text" - like when you set the actual text of a textbox, you can see it in the preview, not just when you run the application.
I have tried setting the Text property to the relevant Ghost text in the OnInitialised function, but it doesn't have any effect on the preview.
Where should I be putting code that affects the preview of a control in the designer?
Bonus question: Is there an actual name for what I call "ghost" textboxes? Would be good to know for the future!
Is there an actual name for what I call "ghost" textboxes? Would be god to know for the future!
I have seen this referred to as a "hint" when describing its purpose, or as a "watermark" when describing its appearance. I tend to employ the former, as it describes the function, which is more in line with the WPF design philosophy: the actual presentation is determined by the template, and the conceptual "hint" could be presented differently simply by applying a custom style/template. Why imply that it should be a watermark when someone could choose to present it in another way?
Design-wise, I think you're approaching this the wrong way. I would implement this such a way that controls other than a TextBox could more easily opt in: use attached properties.
I would create a static class, say HintProperties, which declares a couple of attached dependency properties:
Hint - declares the hint content; typically a string, but it need not be. It could simply be an object, akin to the Content property of a ContentControl.
HasHint - a computed, read-only bool property that gets reevaluated when Hint changes, and simply indicates whether a control has a Hint specified. Useful as a Trigger condition to toggle the visibility of a hint presenter in your control template.
Then, provide a custom style for your TextBox (or other control) which overlays a Hint presenter atop the regular content, hidden by default. Add a trigger to reduce the opacity of the hint when the control has keyboard focus, and another to make the hint Visible when Text is an empty string.
If you really want to go all-out, you can throw in HintTemplate and HintTemplateSelector properties.
However, if this seems like overkill, you can simply declare a Hint or Watermark property directly on your derived TextBox class. I would not try to implement this by conditionally changing the Text property, as that would interfere with data binding and, potentially, value precedence.
You can do this in a reusable way using a style which you would typically declare in your App.xaml. In this style you replace the control template with your own implementation and wrap together some controls. Basically you make up the WatermarkTextBox from a normal TextBox with a transparent background and place a TextBlock control with standard text behind the TextBox. The Visibility of this TextBlock is bound to the TextBox using a specific TextInputToVisibilityConverter so it will disappear when the TextBox has text or just has the focus.
While this maybe looks like a lot of code, you define this once and you can reuse this whereever you need, just by setting style of the TextBox
Declaration of some resources
xmlns:c="clr-namespace:YourNameSpace.Converters"
<SolidColorBrush x:Key="brushWatermarkBackground" Color="White" />
<SolidColorBrush x:Key="brushWatermarkForeground" Color="LightSteelBlue" />
<c:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />
Declaration of the style:
<Style x:Key="SearchTextBox" TargetType="{x:Type TextBox}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid Background="{StaticResource brushWatermarkBackground}">
<TextBlock Margin="5,5" Text="Search..."
Foreground="{StaticResource brushWatermarkForeground}" >
<TextBlock.Visibility>
<MultiBinding
Converter="{StaticResource TextInputToVisibilityConverter}">
<Binding RelativeSource="{RelativeSource
Mode=FindAncestor, AncestorType=TextBox}"
Path="Text.IsEmpty" />
<Binding RelativeSource="{RelativeSource
Mode=FindAncestor, AncestorType=TextBox}"
Path="IsFocused" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
<Border x:Name="Border" Background="Transparent"
BorderBrush="{DynamicResource SolidBorderBrush}"
BorderThickness="1" Padding="2" CornerRadius="2">
<!-- The implementation places the Content into the
ScrollViewer. It must be named PART_ContentHost
for the control to function -->
<ScrollViewer Margin="0" x:Name="PART_ContentHost"
Style="{DynamicResource SimpleScrollViewer}"
Background="Transparent"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The implementation of the TextInputToVisibilityConverter, which just takes text input, converts to bool and converts this to Visibility. Also keeps Focus into account.
namespace YourNameSpace
{
public class TextInputToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (values[0] is bool && values[1] is bool)
{
bool hasText = !(bool)values[0];
bool hasFocus = (bool)values[1];
if (hasFocus || hasText)
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Now all infrastructure is into place. In your view/usercontrol/window just alter the style of the Textbox and there it is, your watermark textbox..
<TextBox Style="{DynamicResource SearchTextBox}" />
I have a custom dependency property:
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("HeaderProperty", typeof(string), typeof(RadAdjustableSlider));
public string Header
{
get
{
return (string)GetValue(HeaderProperty);
}
set
{
SetValue(HeaderProperty, value);
}
}
I then have a binding in my xaml:
<TextBlock Name="txtHeader" Text="{Binding ElementName=main, Path=Header, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Note that I also have this in the declaration at the top of the xaml file:
x:Name="main"
Finally, I have this constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
}
When I put this control inside of another parent control, the Header textblock is blank. Why?
Edit: This blog says that the correct way to do this is by providing a ValidateValueCallback in the DependencyProperty.Register call, but that seems like quite a bit of plumbing and doesn't explain the way dependency properties behave when interacting with external controls. Am I really going to have to write callback functions for all of my dependency properties?
There is a HeaderedContentControl and HeaderedItemsControl in the framework already...
But if you really want to create your own then you should probably use a TemplateBinding. Try something like this instead:
class MyHeaderedControl : ContentControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(object),
typeof(MyHeaderedControl),
new PropertyMetadata());
public MyHeaderedControl()
{
this.DefaultStyleKey = typeof(MyHeaderedControl);
}
}
Then in your project create a file at "\Themes\Generic.xaml". This is a specially named file and must be in the root of the project then in the Themes folder. It must contain a ResourceDictionary.
<ResourceDictionary
xmlns="..."
xmlns:x="..."
xmlns:c="MyControlLibrary1"
>
<Style TargetType="{x:Type c:MyHeaderedControl>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MyHeaderedControl}">
<StackPanel>
<ContentControl Content="{TemplateBinding Header}" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Also, in your AssemblyInfo.cs add this attribute if it's not there already:
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
So for the overview. The general idea is to create some type of logical control where you have properties and events and logic etc. Then in the same assembly you provide default themes. That is how the controls will be displayed by default. At any place where the controls are used the default templates can be overriden and specific templates can be overridden as usual.
So this is the most pain free way you can add custom content like this to your custom controls! Try it once and it will make sense and not feel to cludgy. If you make more controls just keep adding them to the Generic.xaml file.
As justin.m.chase mentioned above, a custom control is probably the best way to go but UserControls are a common scenario so I'll add my 2c anyway.
A UserControl does not set the DataContent property for you and therefore all your bindings inside your UserControl XAML resolve to the DataContent of where you placed the control.
To change this behaviour, either set the DataContext property inside your usercontrol constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
this.DataContext = this;
}
and then bind like this:
<TextBlock Text="{Binding Header}" />
or don't set the DataContext and bind like this:
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ns:RadAdjustableSlider}}}" />
I am looking into creating type-safe generic controls. This is targeting the (reduced) generics support in WPF 4 and future Silverlight, and will include a hierarchy of generic controls.
I have two questions:
Can you use style setters and template bindings for non-generic properties defined on a generic control?
In Silverlight, is there a value I can use for the default style key in the base class that will allow using the same style in the (temporary) specific-type derived classes? (ComponentResourceKey does not exist in Silverlight, so the setup described below does not work.)
The test generic control below defines two test properties: a non-generic Description property, and a generic Data property. The control sets DefaultStyleKey to a ComponentResourceKey for the control.
Here is how the test control is defined:
public class GenericControl<T> : Control {
static GenericControl( ) {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(GenericControl<T>), new FrameworkPropertyMetadata(
new ComponentResourceKey( typeof(Proxy), "GenericControl`1" )
)
);
}
public static readonly DependencyProperty DescriptionProperty =
DependencyProperty.Register(
"Description", typeof(string), typeof(GenericControl<T>),
new PropertyMetadata( "Default Description" )
);
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register(
"Data", typeof(T), typeof(GenericControl<T>),
new PropertyMetadata( default(T) )
);
public string Description { get { ... } set { ... } }
public T Data { get { ... } set { ... } }
}
Here is the style for the test control in generic.xaml:
<Style x:Key="{ComponentResourceKey {x:Type local:Proxy}, GenericControl`1}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description,
RelativeSource={RelativeSource TemplatedParent}}" />
<TextBlock Text="{Binding Data,
RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here are some examples of how this test control would be declared in xaml:
<ListBox Name="list" ... />
<GenericControl x:TypeArguments="sys:Int32" Description="Count: "
Data="{Binding Items.Count, ElementName=list}" />
<Slider Name="slider" ... />
<GenericControl x:TypeArguments="sys:Double" Description="Slider Value: "
Data="{Binding Value, ElementName=slider}" />
With the current generics support in WPF 4, you cannot use an open generic type as the TargetType of a style or control template (doing so results in a "'GenericControl`1' TargetType does not match type of element 'GenericControl`1'." exception). This has two main consequences, as mentioned in question 1 above:
You must use a normal binding with RelativeSource={RelativeSource TemplatedParent} instead of a TemplateBinding in the control template to reference properties defined by the generic control.
You cannot create a style setter for the Description property, even though it does not depend on the generic type of the control.
For the latter, there is a workaround in WPF: just define the non-generic properties as attached dependency properties on a proxy type. Then you can use AddOwner to "declare" the properties on the generic control, and you can use "ProxyType.Property" syntax in a style setter. Of course, Silverlight does not support AddOwner, and turning what is supposed to be an instance property into an attached property is not ideal in any case, so this is not really a long-term solution.
Aside: It looks like there is a regression in the xaml parsing behavior for types. Using VS2008, I can use {x:Type local:GenericControl`1} to get the open type of the control, which I used as the example type in the ComponentResourceKey. In VS2010 though, this results in the following error: "Character '`' was unexpected in string 'local:GenericControl`1'. Invalid XAML type name.", so I changed it to use the proxy type instead.
I posted this same question to the WPF and the Silverlight forums. There was no response on Silverlight, but here is a summary of the answer for WPF:
To quote Shreedhar, "XAML doesn't currently support specifying open generic types".
Using a closed generic type, such as TargetType="{x:Type local:GenericControl(x:Int32)}", will work for an individual style, but will require copy-and-paste to target the same control for other type parameters.
Can create a default template on the fly for any given type argument using XamlReader and some string replacement, but this leaves something to be desired when creating a new style or template outside the control.
Yes, you can. But your xaml should be based on non-generic type only. For example we made slider control like this...
SliderInt32 -> BaseRange<T> -> BaseSliderControl
We could only define style either on BaseSliderControl or only SliderInt32, but not on BaseRange. We could specify generic as well as non generic properties in BaseRange class, and they work well in SliderInt32.
Even in silverlight, you can introduce a parent class of generic class which can serve as your type key, like as shown in above example "BaseSliderControl" control style always works as long as child does not override it.
GenericControl`1 etc name is not proper fully qualified name, so it will never work.