NoesisGUI

UserControl Tutorial

No modern presentation framework would be complete without the ability to create your own reusable controls. If no existing control has a programmatic interface that naturally represents your concept, go ahead and create a user control or custom control.

User controls can be seen as a composition of existing controls. They contain a logical tree defining its look and tend to have logic that directly interacts with these child elements.

Interface Creation

To implement a UserControl we need a xaml describing the visual aspect and a class implementing the behavior. For this tutorial we are going to implement a Color picker inspired by the implementation found at http://colorpicker.com/.

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="Noesis.Samples.ColorPicker"
  UseLayoutRounding="True">

  <UserControl.Resources>
    <Style x:Key="SpectrumSliderButtonStyle" TargetType="{x:Type RepeatButton}">
      <Setter Property="OverridesDefaultStyle" Value="True"/>
      <Setter Property="UseLayoutRounding" Value="True"/>
      <Setter Property="IsTabStop" Value="False"/>
      <Setter Property="Focusable" Value="False"/>
      <Setter Property="ClickMode" Value="Press"/>
      <Setter Property="Delay" Value="250"/>
      <Setter Property="Interval" Value="100"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type RepeatButton}">
            <Border Background="Transparent" />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    <ControlTemplate x:Key="SpectrumSliderThumbTemplate" TargetType="{x:Type Thumb}">
      <Grid Background="Transparent">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="20"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Path Grid.Column="0" Data="M0,0L8,5 0,10z" Fill="Black" HorizontalAlignment="Right"/>
        <Path Grid.Column="2" Data="M8,0L0,5 8,10z" Fill="Black" HorizontalAlignment="Left"/>
      </Grid>
    </ControlTemplate>
    <ControlTemplate x:Key="SpectrumSliderTemplate" TargetType="{x:Type Slider}">
      <Grid>
        <Rectangle StrokeThickness="1" Stroke="#000000" Width="20">
          <Rectangle.Fill>
            <LinearGradientBrush StartPoint="0.5, 0" EndPoint="0.5, 1">
              <GradientStop Color="#FFFF0000" Offset="0"/>
              <GradientStop Color="#FFFF00FF" Offset="0.1666666"/>
              <GradientStop Color="#FF0000FF" Offset="0.3333333"/>
              <GradientStop Color="#FF00FFFF" Offset="0.5"/>
              <GradientStop Color="#FF00FF00" Offset="0.6666666"/>
              <GradientStop Color="#FFFFFF00" Offset="0.8333333"/>
              <GradientStop Color="#FFFF0000" Offset="1"/>
            </LinearGradientBrush>
          </Rectangle.Fill>
        </Rectangle>
        <Track x:Name="PART_Track">
          <Track.Thumb>
            <Thumb Template="{StaticResource SpectrumSliderThumbTemplate}" Margin="0,-4"/>
          </Track.Thumb>
          <Track.DecreaseRepeatButton>
            <RepeatButton Style="{StaticResource SpectrumSliderButtonStyle}" Command="Slider.DecreaseLarge" />
          </Track.DecreaseRepeatButton>
          <Track.IncreaseRepeatButton>
            <RepeatButton Style="{StaticResource SpectrumSliderButtonStyle}" Command="Slider.IncreaseLarge" />
          </Track.IncreaseRepeatButton>
        </Track>
      </Grid>
    </ControlTemplate>
  </UserControl.Resources>

  <Grid Background="Gray">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

    <!-- Color selected -->
    <Grid Grid.Row="0" Grid.ColumnSpan="2">
      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0">
        <TextBlock Text="{Binding ColorText}" FontSize="15" VerticalAlignment="Center" Width="70"/>
        <Rectangle Width="50" Height="50" Margin="10" StrokeThickness="1" Stroke="#000000">
          <Rectangle.Fill>
            <SolidColorBrush Color="{Binding ColorValue}"/>
          </Rectangle.Fill>
        </Rectangle>
      </StackPanel>
    </Grid>

    <!-- Picker -->
    <Grid Grid.Row="1" Grid.Column="0"
        MouseLeftButtonDown="OnPickerMouseLeftButtonDown"
        MouseLeftButtonUp="OnPickerMouseLeftButtonUp"
        MouseMove="OnPickerMouseMove"
        SizeChanged="OnPickerSizeChanged">
      <Rectangle>
        <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
            <GradientStop Color="White" Offset="0"/>
            <GradientStop Color="{Binding PickerColor}" Offset="1"/>
          </LinearGradientBrush>
        </Rectangle.Fill>
      </Rectangle>
      <Rectangle StrokeThickness="1" Stroke="#000000">
        <Rectangle.Fill>
          <LinearGradientBrush StartPoint="1,0" EndPoint="0,0">
            <GradientStop Offset="0" Color="#00000000"/>
            <GradientStop Offset="1" Color="#FF000000"/>
          </LinearGradientBrush>
        </Rectangle.Fill>
      </Rectangle>

      <Canvas Height="12" Width="12" RenderTransformOrigin="0.5,0.5">
        <Canvas.RenderTransform>
          <TranslateTransform X="{Binding PickerX}" Y="{Binding PickerY}"/>
        </Canvas.RenderTransform>
        <Grid>
          <Ellipse Stroke="Black" Width="12" Height="12"/>
          <Ellipse Stroke="White" Width="10" Height="10"/>
        </Grid>
      </Canvas>

    </Grid>

    <!-- Spectrum -->
    <Grid Grid.Row="1" Grid.Column="1">
      <Slider Template="{StaticResource SpectrumSliderTemplate}"
        Orientation="Vertical" IsMoveToPointEnabled="True"
        Minimum="0" Maximum="360" Value="0" Margin="5,0"
        ValueChanged="SpectrumValueChanged"/>
    </Grid>

  </Grid>

</UserControl>

The attribute x:Class indicates the class that implements the code-behind. The implementation follows the steps in the tutorial that describes how to extend NoesisGUI. The unique new detail here, apart from deriving from UserControl, is the GUI.LoadComponent call that indicates the XAML file that will be loaded when this user control is created.

If you need to cache nodes from the xaml and interact with the tree the recommended place to do it is when Loaded event is raised.

This sample exposes the control information to the UI via properties that are then bound in the xaml.

using Noesis;
using System.ComponentModel;

namespace Noesis.Samples
{
public class ColorPicker : UserControl, INotifyPropertyChanged
{
    public ColorPicker()
    {
        InitializeComponent();

        DataContext = this;

        Update();
    }

    public static DependencyProperty ColorProperty = DependencyProperty.Register(
        "Color", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(null));

    public SolidColorBrush Color
    {
        get { return (SolidColorBrush)GetValue(ColorProperty); }
        set { SetValue(ColorProperty, value); }
    }

    public Color PickerColor
    {
        get { return pickerColor; }
        private set { if (pickerColor != value) { pickerColor = value; Changed("PickerColor"); } }
    }
    private Color pickerColor;

    public float PickerX
    {
        get { return pickerX; }
        private set { if (pickerX != value) { pickerX = value; Changed("PickerX"); } }
    }
    private float pickerX;

    public float PickerY
    {
        get { return pickerY; }
        private set { if (pickerY != value) { pickerY = value; Changed("PickerY"); } }
    }
    private float pickerY;

    public Color ColorValue
    {
        get { return colorValue; }
        private set { if (colorValue != value) { colorValue = value; Changed("ColorValue"); } }
    }
    private Color colorValue;

    public string ColorText
    {
        get { return colorText; }
        private set { if (colorText != value) { colorText = value; Changed("ColorText"); } }
    }
    private string colorText;

    public event PropertyChangedEventHandler PropertyChanged;

    private void Changed(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private void InitializeComponent()
    {
        GUI.LoadComponent(this, "Assets/Test/ColorPicker.xaml");
    }

    protected override void Connect(object source, string eventName, string handlerName)
    {
        if (eventName == "ValueChanged" && handlerName == "SpectrumValueChanged")
        {
            ((Slider)source).ValueChanged += SpectrumValueChanged;
        }
        else if (eventName == "MouseLeftButtonDown" && handlerName == "OnPickerMouseLeftButtonDown")
        {
            ((Grid)source).MouseLeftButtonDown += OnPickerMouseLeftButtonDown;
        }
        else if (eventName == "MouseLeftButtonUp" && handlerName == "OnPickerMouseLeftButtonUp")
        {
            ((Grid)source).MouseLeftButtonUp += OnPickerMouseLeftButtonUp;
        }
        else if (eventName == "MouseMove" && handlerName == "OnPickerMouseMove")
        {
            ((Grid)source).MouseMove += OnPickerMouseMove;
        }
        else if (eventName == "SizeChanged" && handlerName == "OnPickerSizeChanged")
        {
            ((Grid)source).SizeChanged += OnPickerSizeChanged;
        }
    }

    private void SpectrumValueChanged(float oldValue, float newValue)
    {
        H = newValue;
        Update();
    }

    private void OnPickerMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Focus();

        UIElement picker = (UIElement)sender;
        picker.CaptureMouse();

        Point p = picker.PointFromScreen(e.GetPosition(this));
        UpdatePickerPosition(picker.RenderSize, p);
    }

    private void OnPickerMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        UIElement picker = (UIElement)sender;
        picker.ReleaseMouseCapture();
    }

    private void OnPickerMouseMove(object sender, MouseEventArgs e)
    {
        UIElement picker = (UIElement)sender;
        if (picker.IsMouseCaptured)
        {
            Point p = picker.PointFromScreen(e.GetPosition(this));
            UpdatePickerPosition(picker.RenderSize, p);
        }
    }

    private void OnPickerSizeChanged(object sender, SizeChangedEventArgs e)
    {
        Size size = e.NewSize;
        PickerX = V * size.Width - 0.5f * size.Width;
        PickerY = size.Height - S * size.Height - 0.5f * size.Height;
    }

    private void UpdatePickerPosition(Size size, Point pos)
    {
        pos.X = System.Math.Max(0.0f, System.Math.Min(size.Width, pos.X));
        pos.Y = System.Math.Max(0.0f, System.Math.Min(size.Height, pos.Y));

        PickerX = pos.X - 0.5f * size.Width;
        PickerY = pos.Y - 0.5f * size.Height;

        V = pos.X / size.Width;
        S = (size.Height - pos.Y) / size.Height;

        Update();
    }

    private void Update()
    {
        PickerColor = HSVToColor(H, 1, 1);
        ColorValue = HSVToColor(H, S, V);
        ColorText = string.Format("#{0:X2}{1:X2}{2:X2}", ColorValue.Ri, ColorValue.Gi, ColorValue.Bi);

        Color = new SolidColorBrush(ColorValue);
    }

    private static Color HSVToColor(float h, float s, float v)
    {
        float chroma = v * s;
        float hueTag = (h % 360) / 60;
        float x = chroma * (1 - System.Math.Abs(hueTag % 2.0f - 1));
        float m = v - chroma;

        switch ((int)hueTag)
        {
            case 0: return new Color(chroma + m, x + m, m);
            case 1: return new Color(x + m, chroma + m, m);
            case 2: return new Color(m, chroma + m, x + m);
            case 3: return new Color(m, x + m, chroma + m);
            case 4: return new Color(x + m, m, chroma + m);
            default: return new Color(chroma + m, m, x + m);
        }
    }

    private float H = 0;
    private float S = 1;
    private float V = 1;
}
}
UserControlTutorialImg3.jpg
© 2017 Noesis Technologies