Tải bản đầy đủ - 0 (trang)
17-14. Create a Lookless Custom Control

17-14. Create a Lookless Custom Control

Tải bản đầy đủ - 0trang

CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



Solution

Create a lookless custom control class that contains interaction and behavior logic but little or no

assumptions about its visual implementation. Then declare the default visual elements for it in a control

template within a default style.



■ Tip When creating the code for a custom control, you need to ensure it is lookless and assumes as little as

possible about the actual implementation of the visual elements in the control template, because it could be

different across different consumers. This means ensuring that the UI is decoupled from the interaction logic by

using commands and bindings, avoiding event handlers, and referencing elements in the ControlTemplate

whenever possible.



How It Works

The first step in creating a lookless custom control is choosing which control to inherit from. You could

derive from the most basic option available to you, because it provides the minimum required

functionality and gives the control consumer the maximum freedom. On the other hand, it also makes

sense to leverage as much built-in support as possible by deriving from an existing WPF control if it

possesses similar behavior and functionality to your custom control. For example, if your control will be

clickable, then it might make sense to inherit from the Button class. If your control is not only clickable

but also has the notion of being in a selected or unselected state, then it might make sense to inherit

from ToggleButton.

Some of the most common base classes you will derive from are listed in Table 17-5.

Table 17-5. Common Base Classes for Creating a Custom Control



Name



Description



FrameworkElement



This is usually the most basic element from which you will derive. Use this when

you need to draw your own element by overriding the OnRender method and

explicitly defining the component visuals. FrameworkElement classes tend not to

interact with the user; for example, the WPF Image and Border controls are

FrameworkElement classes.



Control



Control is the base class used by most of the existing WPF controls. It allows you to

define its appearance by using control templates, and it adds properties for setting

the background and foreground, font, padding, tab index, and alignment of content.

It also supports double-clicking through the MouseDoubleClick and

PreviewMouseDoubleClick events.



827



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



Name



Description



ContentControl



This inherits from Control and adds a Content property that provides the ability to

contain a single piece of content, which could be a string or another visual element.

For example, a button ultimately derives from ContentControl, which is why it has

the ability to contain any arbitrary visual element such as an image. Use this as your

base class if you need your control to contain other objects defined by the control

consumer.



Panel



This has a property called Children that contains a collection of

System.Windows.UIElements, and it provides the layout logic for positioning these

children within it.



Decorator



This wraps another control to decorate it with a particular visual effect or feature.

For example, the Border is a Decorator control that draws a line around an element.



After choosing an appropriate base class for your custom control, you can create the class and put

the logic for the interaction, functionality, and behavior of your control in the custom control class.

However, don’t define your visual elements in a XAML file for the class, like you would with a user

control. Instead, put the default definition of visual elements in a System.Windows.ControlTemplate, and

declare this ControlTemplate in a default System.Windows.Style.

The next step is to specify that you will be providing this new style; otherwise, your control will

continue to use the default template of its base class. You specify this by calling the OverrideMetadata

method of DefaultStyleKeyProperty in the static constructor for your class.

Next, you need to place your style in the Generic.xaml resource dictionary in the Themes subfolder of

your project. This ensures it is recognized as the default style for your control. You can also create other

resource dictionaries in this subfolder, which enables you to target specific operating systems and give

your custom controls a different visual appearance for each one.



■ Tip When a custom control library contains several controls, it is often better the keep their styles separate

instead of putting them all in the same Generic.xaml resource dictionary. You can use resource dictionary

merging to keep each style in a separate resource dictionary file and then merge them into the main

Generic.xaml one.



The custom style and template for your control must use the System.Type.TargetType attribute to

attach it to the custom control automatically.



828



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



■ Tip In Visual Studio, when you add a new WPF custom control to an existing project, it does a number of the

previous steps for you. It automatically creates a code file with the correct call to

DefaultStyleKeyproperty.OverrideMetadata. It creates the Themes subfolder and Generic.xaml resource

dictionary if they don’t already exist, and it defines a placeholder Style and ControlTemplate in there.



When creating your custom control class and default control template, you have to remember to

make as few assumptions as possible about the actual implementation of the visual elements. This is in

order to make the custom control as flexible as possible and to give control consumers as much freedom

as possible when creating new styles and control templates. You can enable this separation between the

interaction logic and the visual implementation of your control in a number of ways.

First, when binding a property of a visual element in the default ControlTemplate to a dependency

property of the control, use the System.Windows.Data.RelativeSource property instead of naming the

element and referencing it via the ElementName property.

Second, instead of declaring event handlers in the XAML for the template—for example, for the

Click event of a Button—either add the event handler programmatically in the control constructor or

bind to commands. If you choose to use event handlers and bind them programmatically, override the

OnApplyTemplate method and locate the controls dynamically.

Furthermore, give names only to those elements without which the control would not be able to

function as intended. By convention, give these intrinsic elements the name PART_ElementName so that

they can be identified as part of the public interface for your control. For example, it is intrinsic to a

ProgressBar that it has a visual element representing the total value at completion and a visual element

indicating the relative value of the current progress. The default ControlTemplate for the

System.Windows.Controls.ProgressBar therefore defines two named elements, PART_Track and

PART_Indicator. These happen to be Border controls in the default template, but there is no reason why a

control consumer could not provide a custom template that uses different controls to display these

functional parts.



■ Tip If your control requires named elements, as well as using the previously mentioned naming convention,

apply the System.Windows.TemplatePart attribute to your control class, which documents and signals this

requirement to users of your control and to design tools such as Expression Blend.



The following code example demonstrates how to separate the interaction logic and the visual

implementation using these methods.



The Code

The following example demonstrates how to create a lookless custom control to encapsulate the

functionality of browsing to a file and displaying the file name. Figure 17-11 shows the control in use.

The FileInputControl class derives from Control and uses the TemplatePart attribute to signal that

it expects a Button control called PART_Browse. It overrides the OnApplyTemplate method and calls



829



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



GetTemplateChild to find the button defined by its actual template. If this exists, it adds an event handler

to the button’s Click event. The code for the control is as follows:

using

using

using

using



System.Windows;

System.Windows.Controls;

System.Windows.Markup;

Microsoft.Win32;



namespace Apress.VisualCSharpRecipes.Chapter17

{

[TemplatePart(Name = "PART_Browse", Type = typeof(Button))]

[ContentProperty("FileName")]

public class FileInputControl : Control

{

static FileInputControl()

{

DefaultStyleKeyProperty.OverrideMetadata(

typeof(FileInputControl),

new FrameworkPropertyMetadata(

typeof(FileInputControl)));

}

public override void OnApplyTemplate()

{

base.OnApplyTemplate();

Button browseButton = base.GetTemplateChild("PART_Browse") as Button;

if (browseButton != null)

browseButton.Click += new RoutedEventHandler(browseButton_Click);

}

void browseButton_Click(object sender, RoutedEventArgs e)

{

OpenFileDialog dlg = new OpenFileDialog();

if (dlg.ShowDialog() == true)

{

this.FileName = dlg.FileName;

}

}

public string FileName

{

get

{

return (string)GetValue(FileNameProperty);

}



830



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



set

{

SetValue(FileNameProperty, value);

}

}

public static readonly DependencyProperty FileNameProperty =

DependencyProperty.Register( "FileName", typeof(string),

typeof(FileInputControl));

}

}

The default style and control template for FileInputControl is in a ResourceDictionary in the Themes

subfolder and is merged into the Generic ResourceDictionary. The XAML for this style is as follows:


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:Apress.VisualCSharpRecipes.Chapter17;assembly=">





The XAML for the window that consumes this custom control is as follows:


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:Apress.VisualCSharpRecipes.Chapter17;assembly="



831



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



Title="Recipe17_14" Height="200" Width="300">








TargetType="{x:Type local:FileInputControl}">


BorderBrush="{TemplateBinding BorderBrush}"

BorderThickness="{TemplateBinding BorderThickness}">






Margin="5, 0, 0, 0" FontSize="16px" FontWeight="Bold"

Text="{Binding Path=FileName,

RelativeSource=

{RelativeSource TemplatedParent}}" />



























832



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



Figure 17-11. Creating and using a FileInput custom control



17-15. Create a Two-Way Binding

Problem

You need to create a two-way binding so that when the value of either property changes, the other one

automatically updates to reflect it.



Solution

Use the System.Windows.Data.Binding markup extension, and set the Mode attribute to System.Windows.

Data.BindingMode.TwoWay. Use the UpdateSourceTrigger attribute to specify when the binding source

should be updated.



How It Works

The data in a binding can flow from the source property to the target property, from the target property

to the source property, or in both directions. For example, suppose the Text property of a

System.Windows.Controls.TextBox control is bound to the Value property of a System.Windows.

Controls.Slider control. In this case, the Text property of the TextBox control is the target of the

binding, and the Value property of the Slider control is the binding source. The direction of data flow

between the target and the source can be configured in a number of different ways. It could be

configured such that when the Value of the Slider control changes, the Text property of the TextBox is

updated. This is called a one-way binding. Alternatively, you could configure the binding so that when

the Text property of the TextBox changes, the Slider control’s Value is automatically updated to reflect

it. This is called a one-way binding to the source. A two-way binding means that a change to either the

source property or the target property automatically updates the other. This type of binding is useful for

editable forms or other fully interactive UI scenarios.

It is the Mode property of a Binding object that configures its data flow. This stores an instance of the

System.Windows.Data.BindingMode enumeration and can be configured with the values listed in Table

17-6.



833



www.it-ebooks.info



CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION



Table 17-6. BindingMode Values for Configuring the Data Flow in a Binding



Value



Description



Default



The Binding uses the default Mode value of the binding target, which varies for each

dependency property. In general, user-editable control properties, such as those of

text boxes and check boxes, default to two-way bindings, whereas most other

properties default to one-way bindings.



OneTime



The target property is updated when the control is first loaded or when the data

context changes. This type of binding is appropriate if the data is static and won’t

change once it has been set.



OneWay



The target property is updated whenever the source property changes. This is

appropriate if the target control is read-only, such as a

System.Windows.Controls.Label or System.Windows.Controls.TextBlock. If the target

property does change, the source property will not be updated.



OneWayToSource



This is the opposite of OneWay. The source property is updated when the target

property changes.



TwoWay



Changes to either the target property or the source automatically update the other.



Bindings that are TwoWay or OneWayToSource listen for changes in the target property and update the

source. It is the UpdateSourceTrigger property of the binding that determines when this update occurs.

For example, suppose you created a TwoWay binding between the Text property of a TextBox control and

the Value property of a Slider control. You could configure the binding so that the slider is updated

either as soon as you type text into the TextBox or when the TextBox loses its focus. Alternatively, you

could specify that the TextBox is updated only when you explicitly call the UpdateSource property of the

System.Windows.Data.BindingExpression class. These options are configured by the Binding’s

UpdateSourceTrigger property, which stores an instance of the System.Windows.Data.

UpdateSourceTrigger enumeration. Table 17-7 lists the possible values of this enumeration.

Therefore, to create a two-way binding that updates the source as soon as the target property

changes, you need to specify TwoWay as the value of the Binding’s Mode attribute and PropertyChanged for

the UpdateSourceTrigger attribute.



■ Note To detect source changes in OneWay and TwoWay bindings, if the source property is not a System.

Windows.DependencyProperty, it must implement System.ComponentModel.INotifyPropertyChanged to notify

the target that its value has changed.



834



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

17-14. Create a Lookless Custom Control

Tải bản đầy đủ ngay(0 tr)

×