CustomControl
与通过控件的组合创建的UserControls相比,CustomControl 扩展了现有控件。可以对CustomControls进行样式设置,通常这是构建控件库的最佳方法。
创建CustomControl非常简单,但是挑战在于正确的方法。因此,在开始创建控件之前,请尝试回答以下问题:
- 我的控件应该解决什么问题?
- 谁将使用此控件?在哪种环境下?
- 我可以扩展或编写现有控件吗?您是否看过现有控件?
- 应该可以为控件设置样式或模板吗?
- 它用于单个项目还是可重用库的一部分?
本教程为您提供有关如何创建表示日期和时间的自定义控件的分步指南。
选择合适的基类
选择正确的基类至关重要,可以节省大量时间。将控件的功能与现有控件进行比较,并从匹配关闭的控件开始。下面的列表应该为您提供从最轻量级到更重量级基本类型的良好概述:
- UIElement:最轻量级的基础类。它支持Layout,Input,Focus和Events。
- FrameworkElement:派生自UIElement并添加了对样式,工具提示和上下文菜单的支持。它是参与逻辑树的第一个基类,因此它支持数据绑定和资源查找。
- 控件:是控件最常见的基类(其名称不言而喻)。它支持模板并添加一些基本属性,例如Foreground,Background和FontSize。
- ContentControl:是具有附加Content属性的控件。这通常用于简单的容器。
- HeaderedContentControl:是一个具有Content和Header属性的控件。这用于标题为Expander,TabControl或GroupBox的控件
- ItemsControl:具有其他Items集合的控件。对于显示动态项目列表而不进行选择的控件,这是一个不错的选择。
- Selector:一个ItemsControl,可以对项目进行索引和选择。这用于ListBox,ComboBox,ListView或TabControl
- RangeBase:是显示诸如Slider或ProgressBar之类的值范围的控件的基类。它添加了Value,Minimum和Maximum属性。
实作
在我们的示例中,我们将从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控件需要一些公共属性,以允许用户修改或显示该控件。我们将添加依赖项属性Day,Month和Year来表示日期,并添加Hour,Minute和Second来表示时间。
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中启用或禁用此功能。
网友评论