customcontrols

来源:百度文库 编辑:神马文学网 时间:2024/04/27 17:40:25
Custom controls
by Me on January 14, 2007 @ 8:50 am, Category:WPF
Styling
A style is much like a class definition in CSS, itdescribes the appearance of an element and can be applied to allelements of the same type or via a resource name to a particularinstance of a type. A style is also a set of properties applied tocontent used for visual rendering. A style can be used to setproperties on an existing visual element, such as setting the fontweight of a Button control, or it can be used to define the way anobject looks, such as showing the name and age from a Person object. Inaddition to the features in word processing styles , WPF styles havespecific features for building applications, including the ability toassociate different visual effects based on user events, provideentirely new looks for existing controls, and even designate renderingbehavior for non-visual objects. All of these features come without theneed to build a custom control.
The following example shows how a named style can be defined in a resource container and applied to instances.
Margin="15" LayoutTransform="scale 2">



TextBox 1
TextBox 2
Unstyled
A style can also be defined inline as follows:

One of the remarkable things with styles is that the set ofdependency properties are applied if the element supports it. Noexception is generated if you define a style property that the elementcannot render.
Styles can inherit from each other by means of the BasedOn attribute as shown in the following example:





Whatever text here

Finally, you can define a trigger in a style based on events, for example here a mouseover effect on a button





Templates
Templates extend styles by describing the visual structure of a control or data type;

VisualTree

The control author can define the default ControlTemplate and theapplication author can override the ControlTemplate to reconstruct thevisual structure of the control.
Control templating is one of the many features offered by the WPFstyling and templating model. The styling and templating model providesyou with such great flexibility that in many cases you do not need towrite your own controls. A ControlTemplate is intended to be aself-contained unit of implementation detail that is invisible tooutside users and objects, including styles. The only way to manipulatethe content of the control template is from within the same controltemplate.
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">




Derivations

You can derive from Control, ContentControl, UserControl and FrameworkElement.
Control
If you add a custom control via a "New Item..." in Visual Sutdio 2005 you will get a control inheriting from Controland themed via the generic.xaml file under the theme directory. Theconstructor code shows something like this to enable themes:
static CustomControl1()
{
//This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
//This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1),new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
In the XAML code you can add your custom control via the usual aliasingtag. What is important to know here is that the generic style can beoverriden in the application as follows;
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FromFrameworkElement" Height="300" Width="300"
xmlns:local="clr-namespace:FromFrameworkElement"
>




Height="66"/>   

It means that you can define your custom control inside a separateassembly, define a default or generic style linked to some C# code andthen hand it over to some consuming application. Inside the applicationyou simply define your own style and all the code-behind is kept alivewithout problems. It really is like skinning an existing control.Wonderful uh?
FrameworkElement
If you intend to use FrameworkElement as a base class, you mightwant to first examine the existing derived classes. FrameworkElementprovides support for a number of basic scenarios, but also lacks anumber of features that are desirable for an "element" in the sense ofa building block that you use to create user interface (UI) inExtensible Application Markup Language (XAML). For instance, aFrameworkElement does not define any true content model; you cannotplace a child element within a true FrameworkElement in ExtensibleApplication Markup Language (XAML). In particular, you might want tolook at Control and ContentControl.
Inheriting from FrameworkElement also does not allow you (out of the box) to template the control as in the example above.
It‘s not an easy task to give you guidance in choosing the rightclass to inherit from. I have found that you need to dig deep in theclass hierarchy to see what is right and wrong. One place to start istheMSDN documentation on base clases. In most circumstance I reckon the ContentControl is a better choice than the FrameworkElement class.
Along the ride you should have a look at the Microsoft.Windows.Themes namespace with the four predefined themes.
One thing you should be surprised about is the fact that inoverriding the OnRender method of the FrameworkElement we‘re back tothe mediaval days of rendering controls, as I learned from Petzold‘sexample;
public class BetterEllipse : FrameworkElement
{
// Dependency properties.
public static readonly DependencyProperty FillProperty;
public static readonly DependencyProperty StrokeProperty;
// Public interfaces to dependency properties.
public Brush Fill
{
set { SetValue(FillProperty, value); }
get { return (Brush)GetValue(FillProperty); }
}
public Pen Stroke
{
set { SetValue(StrokeProperty, value); }
get { return (Pen)GetValue(StrokeProperty); }
}
// Static constructor.
static BetterEllipse()
{
FillProperty =
DependencyProperty.Register("Fill",typeof(Brush),
typeof(BetterEllipse),new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender));
StrokeProperty =
DependencyProperty.Register("Stroke",typeof(Pen),
typeof(BetterEllipse),new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsMeasure));
}
// Override of MeasureOverride.
protected override Size MeasureOverride(Size sizeAvailable)
{
Size sizeDesired = base.MeasureOverride(sizeAvailable);
if (Stroke != null)
sizeDesired =new Size(Stroke.Thickness, Stroke.Thickness);
return sizeDesired;
}
// Override of OnRender.
protected override void OnRender(DrawingContext dc)
{
Size size = RenderSize;
// Adjust rendering size for width of Pen.
if (Stroke != null)
{
size.Width = Math.Max(0, size.Width - Stroke.Thickness);
size.Height = Math.Max(0, size.Height - Stroke.Thickness);
}
// Draw the ellipse.
dc.DrawEllipse(Fill, Stroke,
new Point(RenderSize.Width / 2, RenderSize.Height / 2),
size.Width / 2, size.Height / 2);
}
}
The FrameworkElementFactory
If you want to template controls you better have a look at thefactories inside the framework which help you build up the visual tree,here‘s an example:
public BuildButtonFactory()
{
Title = "Build Button Factory";
// Create a ControlTemplate intended for a Button object.
ControlTemplate template =new ControlTemplate(typeof(Button));
// Create a FrameworkElementFactory for the Border class.
FrameworkElementFactory factoryBorder =
new FrameworkElementFactory(typeof(Border));
// Give it a name to refer to it later.
factoryBorder.Name = "border";
// Set certain default properties.
factoryBorder.SetValue(Border.BorderBrushProperty, Brushes.Red);
factoryBorder.SetValue(Border.BorderThicknessProperty,
new Thickness(3));
factoryBorder.SetValue(Border.BackgroundProperty,
SystemColors.ControlLightBrush);
// Create a FrameworkElementFactory for the ContentPresenter class.
FrameworkElementFactory factoryContent =
new FrameworkElementFactory(typeof(ContentPresenter));
// Give it a name to refer to it later.
factoryContent.Name = "content";
// Bind some ContentPresenter properties to Button properties.
factoryContent.SetValue(ContentPresenter.ContentProperty,
new TemplateBindingExtension(Button.ContentProperty));
// Notice that the button‘s Padding is the content‘s Margin!
factoryContent.SetValue(ContentPresenter.MarginProperty,
new TemplateBindingExtension(Button.PaddingProperty));
// Make the ContentPresenter a child of the Border.
factoryBorder.AppendChild(factoryContent);
// Make the Border the root element of the visual tree.
template.VisualTree = factoryBorder;
// Define a new Trigger when IsMouseOver is true.
Trigger trig =new Trigger();
trig.Property = UIElement.IsMouseOverProperty;
trig.Value = true;
// Associate a Setter with that Trigger to change the
//  CornerRadius property of the "border" element.
Setter set =new Setter();
set.Property = Border.CornerRadiusProperty;
set.Value =new CornerRadius(24);
set.TargetName = "border";
// Add the Setter to the Setters collection of the Trigger.
trig.Setters.Add(set);
// Similarly, define a Setter to change the FontStyle.
// (No TargetName is needed because it‘s the button‘s property.)
set =new Setter();
set.Property = Control.FontStyleProperty;
set.Value = FontStyles.Italic;
// Add it to the same trigger‘s Setters collection as before.
trig.Setters.Add(set);
// Add the Trigger to the template.
template.Triggers.Add(trig);
// Similarly, define a Trigger for IsPressed.
trig =new Trigger();
trig.Property = Button.IsPressedProperty;
trig.Value = true;
set =new Setter();
set.Property = Border.BackgroundProperty;
set.Value = SystemColors.ControlDarkBrush;
set.TargetName = "border";
// Add the Setter to the trigger‘s Setters collection.
trig.Setters.Add(set);
// Add the Trigger to the template.
template.Triggers.Add(trig);
// Finally, create a Button.
Button btn =new Button();
// Give it the template.
btn.Template = template;
// Define other properties normally.
btn.Content = "Button with Custom Template";
btn.Padding =new Thickness(20);
btn.FontSize = 48;
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.VerticalAlignment = VerticalAlignment.Center;
btn.Click += ButtonOnClick;
Content = btn;
}
Routed Commands
Commands are related to events in the sense that it allows you toattach handlers to actions performed by the user in the UI layer. Thewhole mechanism of routed commands and dependency properties is ratherinvolved and I hope to describe more in details later on the way I haveused commands in XPad. For now I only want to record here the way youcan bind command to UI elements in code:

private void HelpCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
// OpenHelpFile opens the help file
OpenHelpFile();
}
private void HelpCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// HelpFilesExists() determines if the help file exists
if (HelpFileExists() == true)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
CommandManager.AddExecutedHandler(helpButton, HelpCmdExecuted);
CommandManager.AddCanExecuteHandler(helpButton, HelpCmdCanExecute);