分享

customcontrols

 louisasea 2007-09-12

Custom controls

by Me on January 14, 2007 @ 8:50 am, Category:WPF   

Styling

A style is much like a class definition in CSS, it describes the appearance of an element and can be applied to all elements of the same type or via a resource name to a particular instance of a type. A style is also a set of properties applied to content used for visual rendering. A style can be used to set properties on an existing visual element, such as setting the font weight of a Button control, or it can be used to define the way an object looks, such as showing the name and age from a Person object. In addition to the features in word processing styles , WPF styles have specific features for building applications, including the ability to associate different visual effects based on user events, provide entirely new looks for existing controls, and even designate rendering behavior for non-visual objects. All of these features come without the need to build a custom control.

The following example shows how a named style can be defined in a resource container and applied to instances.

<stackpanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
   Margin="15" LayoutTransform="scale 2">

   </stackpanel><stackpanel .Resources>
      <style x:Key="MyStyle" TargetType="{x:Type TextBox}">
         <setter Property="Background" Value="Green" />
         <setter Property="Foreground" Value="White" />
      </style>
   </stackpanel>

   <textbox Style="{StaticResource MyStyle}">TextBox 1</textbox>
   <textbox Style="{StaticResource MyStyle}">TextBox 2</textbox>
   <textbox>Unstyled</textbox>

A style can also be defined inline as follows:

<button Width="200" Content="Swa"  Height="66">
</button><button .Style>
<style>
<setter Property="Button.FontSize" Value="26"/>
</style>
</button>

One of the remarkable things with styles is that the set of dependency properties are applied if the element supports it. No exception is generated if you define a style property that the element cannot render.

Styles can inherit from each other by means of the BasedOn attribute as shown in the following example:

<page .Resources>

<style x:Key="CellTextStyle">
  <setter Property="TextElement.FontSize" Value="32" />
  <setter Property="TextElement.FontWeight" Value="Bold" />
</style>
<style x:Key="StatusTextStyle" BasedOn="{StaticResource CellTextStyle}">
  <setter Property="TextElement.FontWeight" Value="Thin" />
  <setter Property="TextElement.Foreground" Value="Orange" />
  <setter Property="TextBlock.HorizontalAlignment" Value="Center" />
</style>

</page>
<textblock Style="{StaticResource StatusTextStyle}">
Whatever text here
</textblock>

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

<page .Resources>

<style x:Key="CellTextStyle">
  <setter Property="TextElement.FontSize" Value="32" />
  <setter Property="TextElement.FontWeight" Value="Bold" />
</style>
<style x:Key="StatusTextStyle" BasedOn="{StaticResource CellTextStyle}" TargetType="{x:Type Button}">
  <setter Property="TextElement.FontWeight" Value="Thin" />
  <setter Property="TextElement.Foreground" Value="Orange" />
  <setter Property="TextBlock.HorizontalAlignment" Value="Center" />
</style><style .Triggers>
<trigger Property="IsMouseOver" Value="True">
      <setter Property="Background" Value="Yellow" />
      <setter Property="FontStyle" Value="Italic" />
    </trigger>

</style>

</page>
<button Style="{StaticResource StatusTextStyle}">

Tadaa
</button>

Templates

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

<controltemplate>
  VisualTree
</controltemplate>

The control author can define the default ControlTemplate and the application author can override the ControlTemplate to reconstruct the visual structure of the control.

Control templating is one of the many features offered by the WPF styling and templating model. The styling and templating model provides you with such great flexibility that in many cases you do not need to write your own controls. A ControlTemplate is intended to be a self-contained unit of implementation detail that is invisible to outside users and objects, including styles. The only way to manipulate the content of the control template is from within the same control template.

<stackpanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">

   </stackpanel><stackpanel .Resources>
      <style x:Key="MyStyle" TargetType="{x:Type Button}">
         <setter Property="Background" Value="SkyBlue" />
         <setter Property="Foreground" Value="White" />
         <setter Property="Cursor" Value="Hand" />
         <setter Property="Template">
            </setter><setter .Value>
               <controltemplate TargetType="{x:Type Button}">
                  <grid>
                     <ellipse Name="rect"
                        Fill="{TemplateBinding Property=Background}" />

                     <contentpresenter Margin="20,0,20,0"
Name="content" TextElement.Foreground="{TemplateBinding Property=Foreground}"
TextElement.FontSize="{TemplateBinding Property=FontSize}"
                                             VerticalAlignment="{TemplateBinding Property=VerticalContentAlignment}"
                                         
   HorizontalAlignment="{TemplateBinding Property=HorizontalContentAlignment}" />

                  </grid>
                  </controltemplate><controltemplate .Triggers>
                     <trigger Property="IsMouseOver" Value="true">
                        <setter TargetName="rect"
Property="Fill" Value="LightBlue" />

                        <setter TargetName="content"
Property="TextElement.Foreground" Value="Black" />

                     </trigger>
                  </controltemplate>
               
            </setter>
         
      </style>
   </stackpanel>
   <button Height="200" Width="400" FontSize="24pt" Style="{StaticResource
MyStyle}"
>
Click here</button>

Derivations

Hierarchy to derive from

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 Control and themed via the generic.xaml file under the theme directory. The constructor 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 aliasing tag. What is important to know here is that the generic style can be overriden in the application as follows;

<window x:Class="FromFrameworkElement.Window1"
    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"
   >

  </window><window .Resources>

    <style TargetType="{x:Type local:CustomControl1}">
      <setter Property="Template">
        </setter><setter .Value>
          <controltemplate TargetType="{x:Type local:CustomControl1}">
            <border Background="Red"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">

            </border>
          </controltemplate>
        </setter>
     
    </style>
  </window>
    <grid>
      <local :CustomControl1 Background="Orange" Width="222"
 Height="66"/>
    </grid>

It means that you can define your custom control inside a separate assembly, define a default or generic style linked to some C# code and then hand it over to some consuming application. Inside the application you simply define your own style and all the code-behind is kept alive without problems. It really is like skinning an existing control. Wonderful uh?

FrameworkElement

If you intend to use FrameworkElement as a base class, you might want to first examine the existing derived classes. FrameworkElement provides support for a number of basic scenarios, but also lacks a number of features that are desirable for an "element" in the sense of a building block that you use to create user interface (UI) in Extensible Application Markup Language (XAML). For instance, a FrameworkElement does not define any true content model; you cannot place a child element within a true FrameworkElement in Extensible Application Markup Language (XAML). In particular, you might want to look 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 right class to inherit from. I have found that you need to dig deep in the class hierarchy to see what is right and wrong. One place to start is the MSDN 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 in overriding the OnRender method of the FrameworkElement we‘re back to the mediaval days of rendering controls, as I learned from Petzold‘s example;

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 the factories 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 to attach handlers to actions performed by the user in the UI layer. The whole mechanism of routed commands and dependency properties is rather involved and I hope to describe more in details later on the way I have used commands in XPad. For now I only want to record here the way you can bind command to UI elements in code:

<button Command="ApplicationCommands.Help"
        Name="helpButton">
Help</button>

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);

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约