Walkthrough: Implementing a Rail Inside a Control
[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]
This walkthrough shows how to create a rail adorner for a Windows Presentation Foundation (WPF) custom control. A rail adorner adds a marker or rule along one side of a control that is non-scaling along a single axis. The adorner is a slider, which is placed within the control. You can use this adorner to set the RenderTransform property on a custom button control. Setting the RenderTransform property skews the control. For a complete code listing, see the Skew Adorner sample from the WPF Designer Extensibility Samples site. .
In this walkthrough, you perform the following tasks:
Create a WPF custom control library project.
Create a separate assembly for design-time metadata.
Implement the adorner provider.
Test the control at design time.
When you are finished, you will know how create an adorner for a custom control.
Note
The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Visual Studio Settings.
Prerequisites
You need the following components to complete this walkthrough:
- Visual Studio 2012 RC.
Creating the Custom Control
The first step is to create the project for the custom control. The control is a simple button with small amount of design-time code, which uses the GetIsInDesignMode method to implement a design-time behavior.
To create the custom control
Create a new WPF Custom Control Library project in Visual Basic or Visual C# named SkewButtonLibrary.
The code for CustomControl1 opens in the Code Editor.
In Solution Explorer, change the name of the code file to SkewButton.cs or SkewButton.vb. If a message box appears that asks if you want to perform a rename for all references in this project, click Yes.
Open SkewButton.cs or SkewButton.vb in the Code Editor.
Replace the automatically generated code with the following code. The SkewButton custom control inherits from Button and displays the text "Design mode active" when the button appears in the designer. The GetIsInDesignMode check and the following design-time code are optional and shown only for demonstration.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Windows.Controls Imports System.Windows.Media Imports System.ComponentModel ' The SkewButton control implements a button ' with a skew transformation applied. Public Class SkewButton Inherits Button Public Sub New() ' The following code enables custom design-mode logic. ' The GetIsInDesignMode check and the following design-time ' code are optional and shown only for demonstration. If DesignerProperties.GetIsInDesignMode(Me) Then Content = "Design mode active" End If End Sub End Class
using System; using System.Collections.Generic; using System.Text; using System.Windows.Controls; using System.Windows.Media; using System.ComponentModel; namespace SkewButtonLibrary { // The SkewButton control implements a button // with a skew transformation applied. public class SkewButton : Button { public SkewButton() { // The following code enables custom design-mode logic. // The GetIsInDesignMode check and the following design-time // code are optional and shown only for demonstration. if (DesignerProperties.GetIsInDesignMode(this)) { Content = "Design mode active"; } } } }
Set the project's output path to "bin\".
Build the solution.
Creating the Design-time Metadata Assembly
Design-time code is deployed in special metadata assemblies. For this walkthrough, the custom adorner is deployed in an assembly named SkewButtonLibrary.VisualStudio.Design. For more information, see Providing Design-time Metadata.
To create the design-time metadata assembly
Add a new Class Library project in Visual Basic or Visual C# named SkewButtonLibrary.VisualStudio.Design to the solution.
Set the project's output path to "..\SkewButtonLibrary\bin\". This keeps the control's assembly and the metadata assembly in the same folder, which enables metadata discovery for designers.
Add references to the following WPF assemblies.
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
Add references to the following WPF Designer assemblies.
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
Add a reference to the SkewButtonLibrary project.
In Solution Explorer, change the name of the Class1 code file to Metadata.cs or Metadata.vb. If a message box appears that asks if you want to perform a rename for all references in this project, click Yes.
Replace the automatically generated code with the following code. This code creates an AttributeTable which attaches the custom design-time implementation to the SkewButton class.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.ComponentModel Imports System.Windows.Media Imports System.Windows.Controls Imports System.Windows Imports Microsoft.Windows.Design.Features Imports Microsoft.Windows.Design.Metadata Imports SkewButtonLibrary.VisualStudio.Design Imports SkewButtonLibrary ' The ProvideMetadata assembly-level attribute indicates to designers ' that this assembly contains a class that provides an attribute table. <Assembly: ProvideMetadata(GetType(SkewButtonLibrary.VisualStudio.Design.Metadata))> ' Container for any general design-time metadata to initialize. ' Designers look for a type in the design-time assembly that ' implements IProvideAttributeTable. If found, designers instantiate ' this class and access its AttributeTable property automatically. Friend Class Metadata Implements IProvideAttributeTable ' Accessed by the designer to register any design-time metadata. Public ReadOnly Property AttributeTable() As AttributeTable _ Implements IProvideAttributeTable.AttributeTable Get Dim builder As New AttributeTableBuilder() ' Add the adorner provider to the design-time metadata. builder.AddCustomAttributes( _ GetType(SkewButton), _ New FeatureAttribute(GetType(SkewButtonAdornerProvider))) Return builder.CreateTable() End Get End Property End Class
using System; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Windows.Media; using System.Windows.Controls; using System.Windows; using SkewButtonLibrary; using Microsoft.Windows.Design.Features; using Microsoft.Windows.Design.Metadata; using SkewButtonLibrary.VisualStudio.Design; [assembly: ProvideMetadata(typeof(SkewButtonLibrary.VisualStudio.Design.Metadata))] namespace SkewButtonLibrary.VisualStudio.Design { // Container for any general design-time metadata to initialize. // Designers look for a type in the design-time assembly that // implements IProvideAttributeTable. If found, designers instantiate // this class and access its AttributeTable property automatically. internal class Metadata : IProvideAttributeTable { // Accessed by the designer to register any design-time metadata. public AttributeTable AttributeTable { get { AttributeTableBuilder builder = new AttributeTableBuilder(); // Add the adorner provider to the design-time metadata. builder.AddCustomAttributes( typeof(SkewButton), new FeatureAttribute(typeof(SkewButtonAdornerProvider))); return builder.CreateTable(); } } } }
Save the solution.
Implementing the Adorner Provider
The adorner provider is implemented in a type named SkewButtonAdornerProvider. This adorner FeatureProvider enables setting the control's RenderTransform property at design time.
To implement the adorner provider
Add a new class named SkewButtonAdornerProvider to the SkewButtonLibrary.VisualStudio.Design project.
In the Code Editor for SkewButtonAdornerProvider, replace the automatically generated code with the following code. This code implements a PrimarySelectionAdornerProvider which provides a custom adorner.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Windows.Input Imports System.Windows Imports System.Windows.Automation Imports System.Windows.Controls Imports System.Windows.Media Imports System.Windows.Shapes Imports Microsoft.Windows.Design.Interaction Imports Microsoft.Windows.Design.Model ' The following class implements an adorner provider for the ' SkewButton control. The adorner is a slider control, which ' changes the SkewTransform of the SkewButton along the x-axis. ' The adorner is placed inside the adorned control. Class SkewButtonAdornerProvider Inherits PrimarySelectionAdornerProvider Private adornedControlModel As ModelItem Private batchedChange As ModelEditingScope Private skewSlider As Slider Private skewSliderAdornerPanel As AdornerPanel Public Sub New() skewSlider = New Slider() End Sub ' The following method is called when the adorner is activated. ' It creates the adorner control, sets up the adorner panel, ' and attaches a ModelItem to the SkewButton. Protected Overrides Sub Activate(ByVal item As ModelItem) ' Save the ModelItem and hook into when it changes. ' This enables updating the slider position when ' a new background value is set. adornedControlModel = item AddHandler adornedControlModel.PropertyChanged, AddressOf AdornedControlModel_PropertyChanged ' Setup the slider's min and max values. skewSlider.Minimum = 0 skewSlider.Maximum = 45 ' Set the slider's background to the rail fill color. skewSlider.Background = AdornerColors.RailFillBrush ' All adorners are placed in an AdornerPanel ' for sizing and layout support. Dim panel As AdornerPanel = Me.Panel ' Position the slider as a rail inside the custom control. AdornerPanel.SetAdornerHorizontalAlignment(skewSlider, AdornerHorizontalAlignment.Stretch) AdornerPanel.SetAdornerVerticalAlignment(skewSlider, AdornerVerticalAlignment.Top) AdornerPanel.SetAdornerMargin(skewSlider, New Thickness(5, 5, 5, 0)) ' Initialize the slider when it is loaded. AddHandler skewSlider.Loaded, AddressOf slider_Loaded ' Handle the value changes of the slider control. AddHandler skewSlider.ValueChanged, AddressOf slider_ValueChanged AddHandler skewSlider.PreviewMouseLeftButtonUp, AddressOf slider_MouseLeftButtonUp AddHandler skewSlider.PreviewMouseLeftButtonDown, AddressOf slider_MouseLeftButtonDown ' Run the base implementation. MyBase.Activate(item) End Sub ' The Panel utility property demand-creates the ' adorner panel and adds it to the provider's ' Adorners collection. Public ReadOnly Property Panel() As AdornerPanel Get If Me.skewSliderAdornerPanel Is Nothing Then Me.skewSliderAdornerPanel = New AdornerPanel() ' Add the adorner to the adorner panel. Me.skewSliderAdornerPanel.Children.Add(skewSlider) ' Add the panel to the Adorners collection. Adorners.Add(skewSliderAdornerPanel) End If Return Me.skewSliderAdornerPanel End Get End Property ' The following method deactivates the adorner. Protected Overrides Sub Deactivate() RemoveHandler adornedControlModel.PropertyChanged, AddressOf AdornedControlModel_PropertyChanged MyBase.Deactivate() End Sub ' The following method handles the Loaded event. ' It assigns the slider control's initial value. Sub slider_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) skewSlider.Value = GetCurrentSkewAngle() End Sub ' The following method handles the PropertyChanged event. ' It updates the slider control's value if the SkewButton control's ' RenderTransform property changed. Sub AdornedControlModel_PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) If e.PropertyName = "RenderTransform" Then ' Assign the SkewButton control's skew angle to the slider. skewSlider.Value = GetCurrentSkewAngle() End If End Sub ' The following method handles the MouseLeftButtonDown event. ' It calls the BeginEdit method on the ModelItem which represents the ' Skewcontrol. Sub slider_MouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) batchedChange = adornedControlModel.BeginEdit() End Sub ' The following method handles the MouseLeftButtonUp event. ' It commits any changes made to the ModelItem which represents the ' Skewcontrol. Sub slider_MouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) If Not (batchedChange Is Nothing) Then batchedChange.Complete() batchedChange.Dispose() batchedChange = Nothing End If End Sub ' The following method handles the slider control's ' ValueChanged event. It sets the value of the ' RenderTransform property by using the ModelProperty ' type. Sub slider_ValueChanged(ByVal sender As Object, ByVal e As RoutedPropertyChangedEventArgs(Of Double)) Dim newSkewValue As Double = e.NewValue ' During setup, don't make a value local and set the skew angle. If newSkewValue = GetCurrentSkewAngle() Then Return End If ' Access the SkewButton control's RenderTransform property ' by using the ModelProperty type. Dim skewProperty As ModelProperty = adornedControlModel.Properties("RenderTransform") If Not skewProperty.IsSet Then ' If the value isn't local, make it local ' before setting a sub-property value. skewProperty.SetValue(skewProperty.ComputedValue) End If ' Set the RenderTransform property on the SkewButton. skewProperty.SetValue(New SkewTransform(newSkewValue, 0)) End Sub ' This utility method gets the SkewControl control's ' skew angle by using the ModelItem. Private Function GetCurrentSkewAngle() Dim skewXform As SkewTransform = adornedControlModel.Properties("RenderTransform").ComputedValue Return skewXform.AngleX End Function End Class
using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; using System.Windows; using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using Microsoft.Windows.Design.Interaction; using Microsoft.Windows.Design.Model; namespace SkewButtonLibrary.VisualStudio.Design { // The following class implements an adorner provider for the // SkewButton control. The adorner is a slider control, which // changes the SkewTransform of the SkewButton along the x-axis. // The adorner is placed inside the adorned control. class SkewButtonAdornerProvider : PrimarySelectionAdornerProvider { private ModelItem adornedControlModel; private ModelEditingScope batchedChange; private Slider skewSlider; private AdornerPanel skewButtonAdornerPanel; public SkewButtonAdornerProvider() { skewSlider = new Slider(); } // The following method is called when the adorner is activated. // It creates the adorner control, sets up the adorner panel, // and attaches a ModelItem to the SkewButton. protected override void Activate(ModelItem item) { // Save the ModelItem and hook into when it changes. // This enables updating the slider position when // a new background value is set. adornedControlModel = item; adornedControlModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler( AdornedControlModel_PropertyChanged); // Setup the slider's min and max values. skewSlider.Minimum = 0; skewSlider.Maximum = 45; // Set the slider's background to the rail fill color. skewSlider.Background = AdornerColors.RailFillBrush; // All adorners are placed in an AdornerPanel // for sizing and layout support. AdornerPanel panel = this.Panel; AdornerPanel.SetAdornerHorizontalAlignment(skewSlider, AdornerHorizontalAlignment.Stretch); AdornerPanel.SetAdornerVerticalAlignment(skewSlider, AdornerVerticalAlignment.Top); AdornerPanel.SetAdornerMargin(skewSlider, new Thickness(5, 5, 5, 0)); // Initialize the slider when it is loaded. skewSlider.Loaded += new RoutedEventHandler(slider_Loaded); // Handle the value changes of the slider control. skewSlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>( slider_ValueChanged); skewSlider.PreviewMouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler( slider_MouseLeftButtonUp); skewSlider.PreviewMouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler( slider_MouseLeftButtonDown); // Run the base implementation. base.Activate(item); } // The Panel utility property demand-creates the // adorner panel and adds it to the provider's // Adorners collection. public AdornerPanel Panel { get { if (this.skewButtonAdornerPanel == null) { skewButtonAdornerPanel = new AdornerPanel(); skewButtonAdornerPanel.Children.Add(skewSlider); // Add the panel to the Adorners collection. Adorners.Add(skewButtonAdornerPanel); } return this.skewButtonAdornerPanel; } } // The following method deactivates the adorner. protected override void Deactivate() { adornedControlModel.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler( AdornedControlModel_PropertyChanged); base.Deactivate(); } // The following method handles the Loaded event. // It assigns the slider control's initial value. void slider_Loaded(object sender, RoutedEventArgs e) { skewSlider.Value = GetCurrentSkewAngle(); } // The following method handles the PropertyChanged event. // It updates the slider control's value if the SkewButton control's // RenderTransform property changed. void AdornedControlModel_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "RenderTransform") { // Assign the SkewButton control's skew angle to the slider. skewSlider.Value = GetCurrentSkewAngle(); } } // The following method handles the MouseLeftButtonDown event. // It calls the BeginEdit method on the ModelItem which represents the // Skewcontrol. void slider_MouseLeftButtonDown( object sender, System.Windows.Input.MouseButtonEventArgs e) { batchedChange = adornedControlModel.BeginEdit(); } // The following method handles the MouseLeftButtonUp event. // It commits any changes made to the ModelItem which represents the // Skewcontrol. void slider_MouseLeftButtonUp( object sender, System.Windows.Input.MouseButtonEventArgs e) { if (batchedChange != null) { batchedChange.Complete(); batchedChange.Dispose(); batchedChange = null; } } // The following method handles the slider control's // ValueChanged event. It sets the value of the // RenderTransform property by using the ModelProperty // type. void slider_ValueChanged( object sender, RoutedPropertyChangedEventArgs<double> e) { double newSkewValue = e.NewValue; // During setup, don't make a value local and set the skew angle. if (newSkewValue == GetCurrentSkewAngle()) { return; } // Access the SkewButton control's RenderTransform property // by using the ModelProperty type. ModelProperty skewProperty = adornedControlModel.Properties["RenderTransform"]; if (!skewProperty.IsSet) { // If the value isn't local, make it local // before setting a sub-property value. skewProperty.SetValue(skewProperty.ComputedValue); } // Set the RenderTransform property on the SkewButton. skewProperty.SetValue(new SkewTransform(newSkewValue, 0)); } // This utility method gets the SkewControl control's // skew angle by using the ModelItem. private double GetCurrentSkewAngle() { SkewTransform skewXform = adornedControlModel.Properties[ "RenderTransform"].ComputedValue as SkewTransform; return skewXform.AngleX; } } }
Build the solution.
Testing the Design-time Implementation
You can use the SkewButton control as you would use any other WPF control. The WPF Designer handles the creation of all design-time objects.
To test the design-time implementation
Add a new WPF Application project named DemoApplication to the solution.
MainWindow.xaml opens in the WPF Designer.
Add a reference to the SkewButtonLibrary project.
In XAML view, replace the automatically generated XAML with the following XAML. This XAML adds a reference to the SkewButtonLibrary namespace and adds the SkewButton custom control. The button appears in Design view with the text "Design mode active", indicating that it is in design mode. If the button does not appear, you might have to click the Information bar at the top of the designer to reload the view.
<Window x:Class="DemoApplication.MainWindow" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:cc="clr-namespace:SkewButtonLibrary;assembly=SkewButtonLibrary" Title="Window1" Height="300" Width="300"> <Grid> <cc:SkewButton Margin="30,30,30,30" Background="#FFD4D0C8"> <cc:SkewButton.RenderTransform> <SkewTransform AngleX="0" AngleY="0" /> </cc:SkewButton.RenderTransform> </cc:SkewButton> </Grid> </Window>
In Design view, click the SkewButton control to select it.
A Slider control appears inside the SkewButton control. If the slider control does not appear, try rebuilding the solution.
Change the value of the slider control.
The control skews as you drag the slider. In XAML view, the RenderTransform property is set to the value specified by the adorner.
Run the DemoApplication project.
At run time, the button is skewed at the angle you set with the adorner.
Next Steps
You can add more custom design-time features to your custom controls.
Add a custom adorner to your control's design time. For more information, see Walkthrough: Creating a Design-time Adorner.
Create an adorner provider for in-place editing. For more information, see Walkthrough: Implementing In-Place Editing.
Create a custom color editor, which you can use in the Properties window. For more information, see Walkthrough: Implementing a Color Editor.
See Also
Reference
PrimarySelectionAdornerProvider
Other Resources
WPF Designer Extensibility Samples