Noesis Gui

教程15:CustomControl

2020-03-14  本文已影响0人  YottaYuan

CustomControl

教程数据

与通过控件组合创建的UserControls相比,CustomControl 扩展了现有控件。可以对CustomControls进行样式设置,通常这是构建控件库的最佳方法。

创建CustomControl非常简单,但是挑战在于正确的方法。因此,在开始创建控件之前,请尝试回答以下问题:

本教程为您提供有关如何创建表示日期和时间的自定义控件的分步指南。

选择合适的基类

选择正确的基类至关重要,可以节省大量时间。将控件的功能与现有控件进行比较,并从匹配关闭的控件开始。下面的列表应该为您提供从最轻量级到更重量级基本类型的良好概述:

实作

在我们的示例中,我们将从Control基类派生,因为我们的控件需要模板来显示日期和时间信息。不需要更复杂的基类添加的其他功能。我们将按照描述如何扩展NoesisGUI 的教程中描述的步骤进行操作。

C ++

class DateTime: public Control
{
    NS_IMPLEMENT_INLINE_REFLECTION(DateTime, Control)
    {
        NsMeta<TypeId>("CustomControl.DateTime");
    }
};

C#

public class DateTime : Control
{
}

覆盖默认样式

控制分离行为外观。该行为在代码中定义。外观由XAML定义。控件使用的默认模板按照惯例包装成具有隐式键的样式。

C ++

<Style TargetType="{x:Type local:DateTime}">
  <Setter Property="Template" Value="{StaticResource AnalogDateTimeTemplate}"/>
</Style>

物产

我们的DateTime控件需要一些公共属性,以允许用户修改或显示该控件。我们将添加依赖项属性DayMonthYear来表示日期,并添加HourMinuteSecond来表示时间。

C ++

class DateTime: public Control
{
public:
    int GetDay() const
    {
        return GetValue<int>(DayProperty);
    }

    void SetDay(int day)
    {
        SetValue<int>(DayProperty, day);
    }

    int GetMonth() const
    {
        return GetValue<int>(MonthProperty);
    }

    void SetMonth(int month)
    {
        SetValue<int>(MonthProperty, month);
    }

    int GetYear() const
    {
        return GetValue<int>(YearProperty);
    }

    void SetYear(int year)
    {
        SetValue<int>(YearProperty, year);
    }

    int GetHour() const
    {
        return GetValue<int>(HourProperty);
    }

    void SetHour(int hour)
    {
        SetValue<int>(HourProperty, hour);
    }

    int GetMinute() const
    {
        return GetValue<int>(MinuteProperty);
    }

    void SetMinute(int minute)
    {
        SetValue<int>(MinuteProperty, minute);
    }

    int GetSecond() const
    {
        return GetValue<int>(SecondProperty);
    }

    void SetSecond(int second)
    {
        SetValue<int>(SecondProperty, second);
    }

    static const DependencyProperty* DayProperty;
    static const DependencyProperty* MonthProperty;
    static const DependencyProperty* YearProperty;
    static const DependencyProperty* HourProperty;
    static const DependencyProperty* MinuteProperty;
    static const DependencyProperty* SecondProperty;

    NS_IMPLEMENT_INLINE_REFLECTION(DateTime, Control)
    {
        NsMeta<TypeId>("CustomControl.DateTime");

        const TypeClass* type = TypeOf<SelfClass>();
        Ptr<ResourceKeyType> defaultStyleKey = ResourceKeyType::Create(type);

        UIElementData* data = NsMeta<UIElementData>(type);
        data->RegisterProperty<int>(DayProperty, "Day",
            FrameworkPropertyMetadata::Create(int(1), FrameworkPropertyMetadataOptions_None));
        data->RegisterProperty<int>(MonthProperty, "Month",
            FrameworkPropertyMetadata::Create(int(1), FrameworkPropertyMetadataOptions_None));
        data->RegisterProperty<int>(YearProperty, "Year",
            FrameworkPropertyMetadata::Create(int(2000), FrameworkPropertyMetadataOptions_None));
        data->RegisterProperty<int>(HourProperty, "Hour",
            FrameworkPropertyMetadata::Create(int(0), FrameworkPropertyMetadataOptions_None));
        data->RegisterProperty<int>(MinuteProperty, "Minute",
            FrameworkPropertyMetadata::Create(int(0), FrameworkPropertyMetadataOptions_None));
        data->RegisterProperty<int>(SecondProperty, "Second",
            FrameworkPropertyMetadata::Create(int(0), FrameworkPropertyMetadataOptions_None));
        data->OverrideMetadata<Ptr<ResourceKeyType>>(FrameworkElement::DefaultStyleKeyProperty,
            "DefaultStyleKey", FrameworkPropertyMetadata::Create(defaultStyleKey,
            FrameworkPropertyMetadataOptions_None));
    }
};

const DependencyProperty* DateTime::DayProperty;
const DependencyProperty* DateTime::MonthProperty;
const DependencyProperty* DateTime::YearProperty;
const DependencyProperty* DateTime::HourProperty;
const DependencyProperty* DateTime::MinuteProperty;
const DependencyProperty* DateTime::SecondProperty;

C#

public class DateTime : Control
{
    #region Day property
    public static readonly DependencyProperty DayProperty = DependencyProperty.Register(
        "Day", typeof(int), typeof(DateTime), new PropertyMetadata(1));

    public int Day
    {
        get { return (int)GetValue(DayProperty); }
        set { SetValue(DayProperty, value); }
    }
    #endregion

    #region Month property
    public static readonly DependencyProperty MonthProperty = DependencyProperty.Register(
        "Month", typeof(int), typeof(DateTime), new PropertyMetadata(1));

    public int Month
    {
        get { return (int)GetValue(MonthProperty); }
        set { SetValue(MonthProperty, value); }
    }
    #endregion

    #region Year property
    public static readonly DependencyProperty YearProperty = DependencyProperty.Register(
        "Year", typeof(int), typeof(DateTime), new PropertyMetadata(2000));

    public int Year
    {
        get { return (int)GetValue(YearProperty); }
        set { SetValue(YearProperty, value); }
    }
    #endregion

    #region Hour property
    public static readonly DependencyProperty HourProperty = DependencyProperty.Register(
        "Hour", typeof(int), typeof(DateTime), new PropertyMetadata(0));

    public int Hour
    {
        get { return (int)GetValue(HourProperty); }
        set { SetValue(HourProperty, value); }
    }
    #endregion

    #region Minute property
    public static readonly DependencyProperty MinuteProperty = DependencyProperty.Register(
        "Minute", typeof(int), typeof(DateTime), new PropertyMetadata(0));

    public int Minute
    {
        get { return (int)GetValue(MinuteProperty); }
        set { SetValue(MinuteProperty, value); }
    }
    #endregion

    #region Second property
    public int Second
    {
        get { return (int)GetValue(SecondProperty); }
        set { SetValue(SecondProperty, value); }
    }

    public static readonly DependencyProperty SecondProperty = DependencyProperty.Register(
        "Second", typeof(int), typeof(DateTime), new PropertyMetadata(0));
    #endregion
}

范本

在任何应用程序中使用此控件都将要求您为DateTime类型指定一个模板。我们将提供两种不同的方法来演示控件样式和模板的功能。

数码时钟

一个模板将日期和时间显示为数字时钟:

CustomControlTutorialImg1.jpg
<!-- Digital clock style -->
<ControlTemplate x:Key="DigitalDateTimeTemplate" TargetType="{x:Type local:DateTime}">
  <Viewbox>
    <StackPanel>
      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" TextElement.FontSize="40">
        <TextBlock Text="{Binding Hour, StringFormat=G, RelativeSource={RelativeSource TemplatedParent}}"/>
        <TextBlock Text=":"/>
        <TextBlock Text="{Binding Minute, StringFormat=G, RelativeSource={RelativeSource TemplatedParent}}"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" TextElement.FontSize="16">
        <TextBlock Text="{Binding Day, StringFormat=G, RelativeSource={RelativeSource TemplatedParent}}"/>
        <TextBlock Text="/"/>
        <TextBlock Text="{Binding Month, StringFormat=G, RelativeSource={RelativeSource TemplatedParent}}"/>
        <TextBlock Text="/"/>
        <TextBlock Text="{Binding Year, StringFormat=G, RelativeSource={RelativeSource TemplatedParent}}"/>
      </StackPanel>
    </StackPanel>
  </Viewbox>
</ControlTemplate>

<Style x:Key="DigitalStyle">
  <Setter Property="local:DateTime.Template" Value="{StaticResource DigitalDateTimeTemplate}"/>
</Style>

模拟时钟

第二个模板是模拟模拟时钟的尝试:

CustomControlTutorialImg2.jpg
<!-- Converters needed -->
<local:HoursConverter x:Key="DateTimeHoursConverter"/>
<local:MinutesConverter x:Key="DateTimeMinutesConverter"/>
<local:SecondsConverter x:Key="DateTimeSecondsConverter"/>

<!-- Analog clock style -->
<ControlTemplate x:Key="AnalogDateTimeTemplate" TargetType="{x:Type local:DateTime}">
  <Viewbox>
    <Grid Height="200" Width="200">
      <Ellipse StrokeThickness="6" Stretch="Uniform">
        <Ellipse.Stroke>
          <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FF4D4D4D" Offset="1"/>
            <GradientStop Color="Gray"/>
          </LinearGradientBrush>
        </Ellipse.Stroke>
      </Ellipse>
      <Ellipse StrokeThickness="5" Stretch="Uniform" Margin="5" Fill="White">
        <Ellipse.Stroke>
          <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FF333333" Offset="0"/>
            <GradientStop Color="#FF999999" Offset="1"/>
          </LinearGradientBrush>
        </Ellipse.Stroke>
      </Ellipse>

      <Grid Margin="18" TextElement.FontSize="24">
        <TextBlock Text="12" HorizontalAlignment="Center" VerticalAlignment="Top"/>
        <TextBlock Text="3" HorizontalAlignment="Right" VerticalAlignment="Center"/>
        <TextBlock Text="6" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
        <TextBlock Text="9" HorizontalAlignment="Left" VerticalAlignment="Center"/>
      </Grid>

      <Path x:Name="BarS" Data="M100,100L100,20" Stroke="#FF333333" StrokeThickness="2"
                       RenderTransformOrigin="0.5,0.5">
        <Path.RenderTransform>
          <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform Angle="{Binding Second,
                                   Converter={StaticResource DateTimeSecondsConverter},
                                   RelativeSource={RelativeSource TemplatedParent}}"/>
            <TranslateTransform/>
          </TransformGroup>
        </Path.RenderTransform>
      </Path>
      <Path x:Name="BarM" Data="M100,100L100,20" Stroke="Red" StrokeThickness="4"
                       RenderTransformOrigin="0.5,0.5">
        <Path.RenderTransform>
          <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform Angle="{Binding Minute,
                                   Converter={StaticResource DateTimeMinutesConverter},
                                   RelativeSource={RelativeSource TemplatedParent}}"/>
            <TranslateTransform/>
          </TransformGroup>
        </Path.RenderTransform>
      </Path>
      <Path x:Name="BarH" Data="M100,100L100,40" Stroke="#FFCA0000" StrokeThickness="8"
                       RenderTransformOrigin="0.5,0.5">
        <Path.RenderTransform>
          <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform Angle="{Binding Hour,
                                   Converter={StaticResource DateTimeHoursConverter},
                                   RelativeSource={RelativeSource TemplatedParent}}"/>
            <TranslateTransform/>
          </TransformGroup>
        </Path.RenderTransform>
      </Path>

      <Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Width="20" Height="20">
        <Ellipse.Fill>
          <RadialGradientBrush>
            <GradientStop Color="#FF343434" Offset="0.2"/>
            <GradientStop Color="#FF666666" Offset="1"/>
            <GradientStop Color="Gray" Offset="0.95"/>
            <GradientStop Color="#FF999999"/>
            <GradientStop Color="#FF404040" Offset="0.9"/>
          </RadialGradientBrush>
        </Ellipse.Fill>
      </Ellipse>
    </Grid>
  </Viewbox>
</ControlTemplate>

<Style TargetType="{x:Type local:DateTime}">
  <Setter Property="Template" Value="{StaticResource AnalogDateTimeTemplate}"/>
</Style>

转换器

模拟时钟样式需要一些转换器才能将控制属性转换为适当的旋转角度。

C ++

class HoursConverter: public BaseValueConverter
{
public:
    bool TryConvert(BaseComponent* value, const Type* type, BaseComponent* /*parameter*/,
        Ptr<BaseComponent>& result)
    {
        if (Boxing::CanUnbox<int>(value) && type == TypeOf<float>())
        {
            int hours = Boxing::Unbox<int>(value);
            result = Boxing::Box(hours * 30.0f);
            return true;
        }

        return false;
    }

    NS_IMPLEMENT_INLINE_REFLECTION(HoursConverter, BaseValueConverter)
    {
        NsMeta<TypeId>("CustomControl.HoursConverter");
    }
};

class MinutesConverter: public BaseValueConverter
{
public:
    bool TryConvert(BaseComponent* value, const Type* type, BaseComponent* /*parameter*/,
        Ptr<BaseComponent>& result)
    {
        if (Boxing::CanUnbox<int>(value) && type == TypeOf<float>())
        {
            int minutes = Boxing::Unbox<int>(value);
            result = Boxing::Box(minutes * 6.0f);
            return true;
        }

        return false;
    }

    NS_IMPLEMENT_INLINE_REFLECTION(MinutesConverter, BaseValueConverter)
    {
        NsMeta<TypeId>("CustomControl.MinutesConverter");
    }
};

class SecondsConverter: public BaseValueConverter
{
public:
    bool TryConvert(BaseComponent* value, const Type* type, BaseComponent* /*parameter*/,
        Ptr<BaseComponent>& result)
    {
        if (Boxing::CanUnbox<int>(value) && type == TypeOf<float>())
        {
            int seconds = Boxing::Unbox<int>(value);
            result = Boxing::Box(seconds * 6.0f);
            return true;
        }

        return false;
    }

    NS_IMPLEMENT_INLINE_REFLECTION(SecondsConverter, BaseValueConverter)
    {
        NsMeta<TypeId>("CustomControl.SecondsConverter");
    }
};

C#

public class HoursConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int hours = (int)value;
        return hours * 30.0f;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class MinutesConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int minutes = (int)value;
        return minutes * 6.0f;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class SecondsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int seconds = (int)value;
        return seconds * 6.0f;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

改进之处

此控件的一个很酷的功能是可以根据系统时间和日期自动更新。我们可以在控制实例中使用计时器来实现此目的,该计时器在计时器被选中时会更新时间和日期值。然后,我们可以添加一个布尔属性,以允许在XAML中启用或禁用此功能。

上一篇 下一篇

猜你喜欢

热点阅读