User Tools

Site Tools


notes:uwp:customcontrols

Custom Controls in XAML

Create a custom control (a.k.a. templated control) in Visual Studio:

  • Add > New Item > Templated Control > Provide a name for your control, e.g. SimpleControl.
  • VS creates a class SimpleControl that inherits from Control.
  • VS creates a Themes folder with a Generic.xaml file in it.

Alternatively, you may want to create a UserControl first and then change it to a custom control by deriving it from a class different than the UserControl class, for example the Button class.

Example: A simple custom control:

Generic.xaml:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TestApp">
 
    <Style TargetType="local:SimpleControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:SimpleControl">
                    <Grid Height="52" BorderBrush="Blue" BorderThickness="1" Padding="8">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
 
                        <TextBlock x:Name="SimpleLabel" 
                                   Grid.Column="0" 
                                   Margin="0,0,4,0" 
                                   VerticalAlignment="Center" />
                        <TextBox x:Name="SimpleTextBox" 
                                 Grid.Column="1" 
                                 Foreground="Gainsboro"
                                 VerticalAlignment="Center" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

The SimpleControl class:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
 
namespace TestApp
{
    // The TemplatePart attribute allows you to specify the name and type of any parts of your 
    // templated control that you expect to be available for the control to function properly. 
    [TemplatePart(Name = "SimpleLabel", Type = typeof(TextBlock))]
    [TemplatePart(Name = "SimpleTextBox", Type = typeof(TextBox))]
    public sealed class SimpleControl : Control
    {
        private TextBlock SimpleLabel;
        private TextBox SimpleTextBox;
 
        public string LabelText { get; set; }
        public string Text { get; set; }
 
        public SimpleControl()
        {
            // Set the DefaultStyleKey property to the SimpleControl type.
            // This lets the framework know that this control supports templating and also 
            // identifies the key that will be used to find the style for this control.
            this.DefaultStyleKey = typeof(SimpleControl);
        }
 
        protected override void OnApplyTemplate()
        {
            // Retrieve the named elements in the instantiated ControlTemplate visual tree. 
            SimpleLabel = this.GetTemplateChild("SimpleLabel") as TextBlock;
            SimpleTextBox = this.GetTemplateChild("SimpleTextBox") as TextBox;
 
            // Check if the elements are present and set theis values.
            if (SimpleLabel != null && SimpleTextBox != null)
            {
                SimpleLabel.Text = LabelText;
                SimpleTextBox.Text = Text;
            }
        }
    }
}

Example: Another way of creating a custom control is to define a XAML file and a corresponding code-behind file. In the following example we create a custom button GradientButton:

GradientButton.xaml

<Button x:Class="TestApp.GradientButton"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Button.Background>
        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
            <GradientStop Offset="0" Color="{x:Bind ColorBegin}" />
            <GradientStop Offset="1" Color="{x:Bind ColorEnd}" />
        </LinearGradientBrush>
    </Button.Background>
</Button>

GradientButton.xaml.cs

using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
 
namespace TestApp
{
    public sealed partial class GradientButton : Button
    {
        public static readonly DependencyProperty ColorBeginProperty =
            DependencyProperty.Register("ColorBegin", typeof(Color), typeof(GradientButton), 
                new PropertyMetadata(Colors.White));
 
        public static readonly DependencyProperty ColorEndProperty =
            DependencyProperty.Register("ColorEnd", typeof(Color), typeof(GradientButton), 
                new PropertyMetadata(Colors.Black));
 
        public Color ColorBegin
        {
            get { return (Color)GetValue(ColorBeginProperty); }
            set { SetValue(ColorBeginProperty, value); }
        }
 
        public Color ColorEnd
        {
            get { return (Color)GetValue(ColorEndProperty); }
            set { SetValue(ColorEndProperty, value); }
        }
 
        public GradientButton()
        {
            this.InitializeComponent();
        }
    }
}

MainPage.xaml uses the GradientButton:

<Page x:Class="TestApp.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TestApp">
 
    <Grid>
        <local:GradientButton Foreground="White" Content="Test Button"
                              ColorBegin="Red" ColorEnd="Blue" />
    </Grid>
 
</Page>

Example: The PieSlice control is based on an example from the book “Programming Windows” by Charles Petzold:

using System;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
 
namespace TestApp
{
    public class PieSlice : Path
    {
        // Store frequently used objects globally.
        private PathFigure pathFigure;
        private LineSegment lineSegment;
        private ArcSegment arcSegment;
 
        // Animatable properties must be backed by dependency properties.
 
        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Point), typeof(PieSlice),
                new PropertyMetadata(new Point(100, 100), OnPropertyChanged));
 
        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.Register("Radius", typeof(double), typeof(PieSlice),
                new PropertyMetadata(100.0, OnPropertyChanged));
 
        public static readonly DependencyProperty StartAngleProperty =
            DependencyProperty.Register("StartAngle", typeof(double), typeof(PieSlice),
                new PropertyMetadata(0.0, OnPropertyChanged)); // measured in degrees
 
        public static readonly DependencyProperty SweepAngleProperty =
            DependencyProperty.Register("SweepAngle", typeof(double), typeof(PieSlice),
                new PropertyMetadata(90.0, OnPropertyChanged)); // measured in degrees
 
        public Point Center
        {
            set { SetValue(CenterProperty, value); }
            get { return (Point)GetValue(CenterProperty); }
        }
 
        public double Radius
        {
            set { SetValue(RadiusProperty, value); }
            get { return (double)GetValue(RadiusProperty); }
        }
 
        public double StartAngle
        {
            set { SetValue(StartAngleProperty, value); }
            get { return (double)GetValue(StartAngleProperty); }
        }
 
        public double SweepAngle
        {
            set { SetValue(SweepAngleProperty, value); }
            get { return (double)GetValue(SweepAngleProperty); }
        }
 
        public PieSlice()
        {
            pathFigure = new PathFigure { IsClosed = true };
            lineSegment = new LineSegment();
            arcSegment = new ArcSegment { SweepDirection = SweepDirection.Clockwise };
            pathFigure.Segments.Add(lineSegment);
            pathFigure.Segments.Add(arcSegment);
 
            PathGeometry pathGeometry = new PathGeometry();
            pathGeometry.Figures.Add(pathFigure);
 
            this.Data = pathGeometry;
            UpdateValues();
        }
 
        private static void OnPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as PieSlice).UpdateValues();
        }
 
        // UpdateValues is called whenever any of the custom properties (Center, Radius
        // StartAngle, SweepAngle) changes.
        private void UpdateValues()
        {
            pathFigure.StartPoint = this.Center;
 
            double x = this.Center.X + this.Radius * Math.Sin(Math.PI * this.StartAngle / 180);
            double y = this.Center.Y - this.Radius * Math.Cos(Math.PI * this.StartAngle / 180);
            lineSegment.Point = new Point(x, y);
 
            x = this.Center.X + this.Radius * Math.Sin(Math.PI * (this.StartAngle +
                                                                  this.SweepAngle) / 180);
 
            y = this.Center.Y - this.Radius * Math.Cos(Math.PI * (this.StartAngle +
                                                                  this.SweepAngle) / 180);
            arcSegment.Point = new Point(x, y);
            arcSegment.IsLargeArc = this.SweepAngle >= 180;
 
            arcSegment.Size = new Size(this.Radius, this.Radius);
        }
    }
}

Usage:

<Page x:Class="TestApp.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TestApp">
    <Grid>
        <local:PieSlice x:Name="PieSlice1"
                        Center="300,300" Radius="150"
                        Stroke="Blue" StrokeThickness="2" Fill="Wheat" />
    </Grid>
 
    <Page.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="PieSlice1"
                                     Storyboard.TargetProperty="SweepAngle"
                                     EnableDependentAnimation="True"
                                     From="1" To="359" Duration="0:0:5"
                                     AutoReverse="True"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>

Links:

notes/uwp/customcontrols.txt · Last modified: 2017/02/16 by admin