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="Sample.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 x:Name="Text" FontSize="15" VerticalAlignment="Center" Width = "70"/>
        <Rectangle Width="50" Height="50" Margin="10" StrokeThickness="1" Stroke="#000000">
          <Rectangle.Fill>
            <SolidColorBrush x:Name="Color" Color="#FF0000"/>
          </Rectangle.Fill>
        </Rectangle>
      </StackPanel>
    </Grid>

    <!-- Picker -->
    <Grid x:Name="HS" Grid.Row="1" Grid.Column="0">
      <Rectangle>
        <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
            <GradientStop Color="White" Offset="0"/>
            <GradientStop x:Name="Stop" Color="Red" 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"  ClipToBounds="true">
        <Canvas.RenderTransform>
          <TranslateTransform x:Name="PickerTransform" X="0" Y="0"/>
        </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 x:Name="Slider" Orientation="Vertical" IsMoveToPointEnabled="True" Minimum="0" Maximum="360"
        Value="0" Template="{StaticResource SpectrumSliderTemplate}" Margin="5,0"/>
    </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 class attribute UserControlSource. This attribute indicates the XAML file that will be loaded when this user control is initialized. Note that OnPostInit() is the correct place to cache nodes from the xaml and interact with the tree. This method is invoked just after the xaml has been loaded.

using Noesis;
using System;

namespace Sample
{

[Noesis.Extended]
[Noesis.UserControlSource("Assets/Test/ColorPicker.xaml")]
public class ColorPicker : Noesis.UserControl
{
    public static DependencyProperty ColorProperty = DependencyProperty.Register("Color",
        typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(null));

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

    public void OnPostInit()
    {
        _spectrum = FindName<Slider>("Slider");
        _hs = FindName<FrameworkElement>("HS");
        _pickerTransform = FindName<TranslateTransform>("PickerTransform");
        _text = FindName<TextBlock>("Text");
        _color = FindName<SolidColorBrush>("Color");
        _stop = FindName<GradientStop>("Stop");

        _spectrum.ValueChanged += this.OnSpectrumChange;
        _hs.MouseLeftButtonDown += this.OnMouseLeftButtonDown;
        _hs.MouseLeftButtonUp += this.OnMouseLeftButtonUp;
        _hs.MouseMove += this.OnMouseMove;
        _hs.SizeChanged += this.OnSizeChanged;

        _hue = 0.0f;
        _saturation = 0.5f;
        _value = 0.5f;

        Update();
    }

    private void OnSpectrumChange(float oldValue, float newValue)
    {
        _hue = newValue;
        _stop.SetColor(HSVToColor(_hue, 1, 1));
        Update();
    }

    private void OnMouseLeftButtonDown(BaseComponent c, MouseButtonEventArgs args)
    {
        Focus();
        _hs.CaptureMouse();
        Noesis.Point p = _hs.PointFromScreen(args.position);
        UpdatePickerPosition(p);
    }

    private void OnMouseLeftButtonUp(BaseComponent c, MouseButtonEventArgs args)
    {
        _hs.ReleaseMouseCapture();
    }

    private void OnMouseMove(BaseComponent c, MouseEventArgs args)
    {
        if (_hs.GetIsMouseCaptured())
        {
            Noesis.Point p = _hs.PointFromScreen(args.position);
            UpdatePickerPosition(p);
        }
    }

    private void OnSizeChanged(BaseComponent c, SizeChangedEventArgs args)
    {
        Noesis.Size size = args.sizeChangedInfo.newSize;
        _pickerTransform.SetX(_value * size.width - 0.5f * size.width);
        _pickerTransform.SetY(size.height - _saturation * size.height - 0.5f * size.height);
    }

    private void UpdatePickerPosition(Noesis.Point pos)
    {
        Noesis.Size size = _hs.GetRenderSize();

        pos.x = Math.Max(0.0f, Math.Min(size.width, pos.x));
        pos.y = Math.Max(0.0f, Math.Min(size.height, pos.y));

        _pickerTransform.SetX(pos.x - 0.5f * size.width);
        _pickerTransform.SetY(pos.y - 0.5f * size.height);

        _value = pos.x / size.width;
        _saturation = (size.height - pos.y) / size.height;

        Update();
    }

    private void Update()
    {
        Noesis.Color color = HSVToColor(_hue, _saturation, _value);
        int red = color.GetRedI();
        int green = color.GetGreenI();
        int blue = color.GetBlueI();
        _text.SetText(String.Format("#{0:X2}{1:X2}{2:X2}", red, green, blue));
        _color.SetColor(color);
        Color = new SolidColorBrush(color);
    }

    private Noesis.Color HSVToColor(float hue, float saturation, float value)
    {
        float chroma = value * saturation;
        float hueTag = (hue % 360) / 60;
        float x = chroma * (1 - Math.Abs(hueTag % 2.0f - 1));
        float m = value - chroma;

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

    private Slider _spectrum;
    private FrameworkElement _hs;
    private TranslateTransform _pickerTransform;
    private TextBlock _text;
    private SolidColorBrush _color;
    private GradientStop _stop;

    private float _hue;
    private float _saturation;
    private float _value;
}

}
UserControlTutorialImg3.jpg
© 2017 Noesis Technologies