In most cases, we can just utilize the build-in controls WPF provides. Which is very powerful and handy for developing our application. However, in some circumstance, we really need to create our own controls. By doing which can save much time and benefit the powerful template and style functionality that WPF enables. Today lets consider creating custom controls and note some known issues in this process.
First of all, assume the following custom control code, we create a custom WPFButton that inherits from the default Button, and define a ButtonContent dependency property, which is of type object. Meaning we can set whatever object-derived to it. in the XAML code, we just create a simply WPFButton and specify the template for it. Use two ContentPresenters to hold the default Content property value and the custom ButtonContent. Things seem all right for now. Now let’s run the application.
XAML code:
<Window x:Class="WpfApplication22.Window7"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication22"
Title="Window7" Height="300" Width="300">
<Window.Resources>
<local:ContentValueConverter x:Key="selToEn"/>
<Style TargetType="{x:Type local:WPFButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:WPFButton}">
<DockPanel>
<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
Name="Bd"
SnapsToDevicePixels="True">
<StackPanel>
</StackPanel>
</Border>
<!--Content is placed here-->
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" />
<!--ButtonsContent is placed here-->
<ContentPresenter Content="{TemplateBinding ButtonsContent}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<local:WPFButton x:Name="WPFButtonTest">
<local:WPFButton.Content>
<Label Content="I am in WPFButton.Content" Name="labelContent" />
</local:WPFButton.Content>
<local:WPFButton.ButtonsContent>
<StackPanel>
<Button Content="{Binding ElementName=labelContent, Path=Content}"/>
</StackPanel>
</local:WPFButton.ButtonsContent>
</local:WPFButton>
</StackPanel>
</Window>
In the Code behind:
public class WPFButton: ContentControl
{
public WPButtons()
: base()
{
}
static WPButtons()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WPButtons), new FrameworkPropertyMetadata(typeof(WPButtons)));
}
public static readonly DependencyProperty ButtonsContentProperty =
DependencyProperty.Register("ButtonsContent",
typeof(object), typeof(WPButtons),
new PropertyMetadata(null, new PropertyChangedCallback(OnButtonsContentPropertyChanged), new CoerceValueCallback(OnButtonsContentPropertyCoerceValue)));
public object ButtonsContent
{
get { return (object)this.GetValue(ButtonsContentProperty); }
set { this.SetValue(ButtonsContentProperty, value); }
}
public event DependencyPropertyChangedEventHandler ButtonsContentChanged;
private static void OnButtonsContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WPFButtonobj = d as WPButtons;
obj.OnButtonsContentChanged(e);
}
private static object OnButtonsContentPropertyCoerceValue(DependencyObject d, object value)
{
return value;
}
protected virtual void OnButtonsContentChanged(DependencyPropertyChangedEventArgs e)
{
if (this.ButtonsContentChanged != null)
{
this.ButtonsContentChanged(this, e);
}
}
}
Wow, there is a problem. The binding in the Button inside the WPFButton section is not succeed. Why this happened? Let’s have a look at the Output window, oh, it seems that the binding can not find the binding target, in which case is Label. The most possible reason is that they are not in the name namescope.
So far, we have three approaches to resolve this.
1 Use value converter to find the root Button then find the Label.
2 Make Window to expose a reference to the Label control and then bind that reference property.
3 invoke AddChildVisual method to add the ButtonContet into the same namescope to the template.
1 Use value converter to find the root first then find the Label.
We firstly use find ancestor binding syntax to find the top button, then access the Label control
XAML code:
<local:WPButtons.ButtonsContent>
<StackPanel>
<Button Content="{Binding Converter={StaticResource selToEn}, RelativeSource={RelativeSource AncestorType={x:Type local:WPButtons}} }"/>
</StackPanel>
</local:WPButtons.ButtonsContent>
Code:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((value as WPButtons).Content as Label).Content;
}
2 Make Window to expose a reference to the Label control and then bind that reference property.
In this case, we firstly find the root window, then bind the new property.
XAML code:
<local:WPButtons.ButtonsContent>
<StackPanel>
<Button Content="{Binding Path=MyLabel, RelativeSource={RelativeSource AncestorType={x:Type Window}} }"/>
</StackPanel>
</local:WPButtons.ButtonsContent>
Code:
private Label myLabel;
public object MyLabel
{
get { return myLabel.Content ; }
}
public Window7()
{
InitializeComponent();
myLabel = labelContent;
}
3 Invoke AddChildVisual method to add the ButtonContet into the same namescope to the template.
Actually, the essential reason is that the ButttonContent is not added to the namescope of the Button template. In order to add it into the namescope into the ControlTemplate of Button, you can invoke the AddLogicalChild method in the property changed callback function as shown below:
private static void OnButtonsContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WPButtons obj = d as WPButtons;
if (e.OldValue != null)
{
obj.RemoveLogicalChild(e.OldValue);
}
if (e.NewValue != null)
{
obj.AddLogicalChild(e.NewValue);
}
obj.OnButtonsContentChanged(e);
}
In fact, if we dig into the source code, we can find the AddLogicalChild and AddVisualChild method in some build-in control. Like the ContentControl and the HeaderedContentControl.
ContentControl.OnContentChanged:
protected virtual void OnContentChanged(object oldContent, object newContent)
{
base.RemoveLogicalChild(oldContent);
if (!this.ContentIsNotLogical)
{
if (base.TemplatedParent != null)
{
DependencyObject current = newContent as DependencyObject;
if ((current != null) && (LogicalTreeHelper.GetParent(current) != null))
{
return;
}
}
base.AddLogicalChild(newContent);
}
}
System.Windows.Controls.HeaderedContentControl:
protected virtual void OnHeaderChanged(object oldHeader, object newHeader)
{
base.RemoveLogicalChild(oldHeader);
base.AddLogicalChild(newHeader);
}
Thanks.
0 comments:
Post a Comment