Noesis Gui

教程14:UserControl

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

UserControl教程

教程数据

没有创建自己的可重用控件的能力,任何现代演示文稿框架都是不完整的。如果没有现有控件具有自然代表您概念的程序化界面,请继续创建用户控件或自定义控件。

UserControls可以看作是现有控件的组合。它们包含一个定义其外观的逻辑树,并且倾向于具有与这些子元素直接交互的逻辑。

相反,当您要创建全新的控件或扩展现有控件的功能时,需要使用CustomControls。自定义控件往往会从单独的控件模板中定义的视觉树中获得外观,并且通常具有即使用户完全更改其视觉树也能正常工作的逻辑。

在本教程中,我们将专注于实现典型数字微调器的简单用户控件的开发。在接下来的教程将竭诚为自定义控件

界面创建

让我们创建一个非常简单的用户控件,一个NumericUpDown控件,该控件由两个用来增加和减少该值的RepeatButtons和一个用于显示它的TextBlock组成

UserControlTutorialImg1.jpg

NumericUpDown.xaml

<UserControl
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   x:Class="UserControl.NumericUpDown">

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

    <Border Grid.RowSpan="2" Grid.ColumnSpan="2" BorderThickness="1"
            BorderBrush="#40484F" Background="#20282F"/>

    <TextBlock Grid.RowSpan="2" VerticalAlignment="Center" Margin="5,3,4,3" />

    <RepeatButton Name="UpButton" Grid.Column="1" Grid.Row="0" Padding="4,1" Margin="0,2,2,0">
      <Path Data="M1,1L4,4 7,1" Stroke="Black" StrokeThickness="2"
            StrokeStartLineCap="Round" StrokeEndLineCap="Round" RenderTransformOrigin="0.5,0.5">
        <Path.RenderTransform>
          <ScaleTransform ScaleY="-1"/>
        </Path.RenderTransform>
      </Path>
    </RepeatButton>

    <RepeatButton Name="DownButton" Grid.Column="1" Grid.Row="1" Padding="4,1" Margin="0,0,2,2">
      <Path Data="M1,1L4,4 7,1" Stroke="Black" StrokeThickness="2"
            StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
    </RepeatButton>

  </Grid>

</UserControl>

该接口将对应于实现控件代码背后的C ++类:

NumericUpDown.xaml.cpp

class NumericUpDown: public UserControl
{
public:
    NumericUpDown()
    {
        InitializeComponent();
    }

private:
    void InitializeComponent()
    {
        GUI::LoadComponent(this, "NumericUpDown.xaml");
    }

    NS_IMPLEMENT_INLINE_REFLECTION(NumericUpDown, UserControl)
    {
        NsMeta<TypeId>("UserControl.NumericUpDown");
    }
};

我们正在按照描述如何扩展NoesisGUI 的教程中的步骤进行操作。除了派生自UserControl之外,此处唯一的新细节是GUI :: LoadComponent调用,该调用指示创建此用户控件时将加载的XAML文件。

物产

现在,我们定义此控件将公开的属性。我们讨论了数字微调器值,因此这可能是我们的第一个属性:Value。必须将一个依赖项属性声明为具有getter和setter访问器的公共静态成员,以便于在代码内使用控件:

NumericUpDown.xaml.cpp

class NumericUpDown: public UserControl
{
public:
    NumericUpDown()
    {
        InitializeComponent();
    }

    int GetValue() const
    {
        return DependencyObject::GetValue<int>(ValueProperty);
    }

    void SetValue(int value)
    {
        DependencyObject::SetValue<int>(ValueProperty, value);
    }

    static const DependencyProperty* ValueProperty;

private:
    void InitializeComponent()
    {
        GUI::LoadComponent(this, "NumericUpDown.xaml");
    }

    NS_IMPLEMENT_INLINE_REFLECTION(NumericUpDown, UserControl)
    {
        NsMeta<TypeId>("UserControl.NumericUpDown");

        UIElementData* data = NsMeta<UIElementData>(TypeOf<SelfClass>());
        data->RegisterProperty<int>(ValueProperty, "Value",
            FrameworkPropertyMetadata::Create(int(0), FrameworkPropertyMetadataOptions_None));
    }
};

const DependencyProperty* NumericUpDown::ValueProperty;

该界面有一个文本框,用于显示微调框的值。如果要自动更新接口,可以将其Text属性绑定到我们刚刚创建的Value属性。除此之外,该界面还有两个按钮,用于增加或减少微调器的值,因此我们可以在代码后添加一些代码,以响应这些按钮的Click事件以适当地更新Value属性:

NumericUpDown.xaml

<UserControl
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   x:Class="UserControl.NumericUpDown"
   x:Name="NumericUpDownControl">

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

    <Border Grid.RowSpan="2" Grid.ColumnSpan="2" BorderThickness="1"
            BorderBrush="#40484F" Background="#20282F"/>

    <TextBlock Grid.RowSpan="2" VerticalAlignment="Center" Margin="5,3,4,3"
               Text="{Binding Value, ElementName=NumericUpDownControl}"/>

    <RepeatButton Name="UpButton" Grid.Column="1" Grid.Row="0" Padding="4,1"
                  Margin="0,2,2,0" Click="UpButton_Click">
      <Path Data="M1,1L4,4 7,1" Stroke="Black" StrokeThickness="2"
            StrokeStartLineCap="Round" StrokeEndLineCap="Round" RenderTransformOrigin="0.5,0.5">
        <Path.RenderTransform>
          <ScaleTransform ScaleY="-1"/>
        </Path.RenderTransform>
      </Path>
    </RepeatButton>

    <RepeatButton Name="DownButton" Grid.Column="1" Grid.Row="1" Padding="4,1"
                  Margin="0,0,2,2" Click="DownButton_Click">
      <Path Data="M1,1L4,4 7,1" Stroke="Black" StrokeThickness="2"
            StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
    </RepeatButton>

  </Grid>

</UserControl>

NumericUpDown.xaml.cpp

class NumericUpDown: public UserControl
{
public:
    NumericUpDown()
    {
        InitializeComponent();
    }

    int GetValue() const
    {
        return DependencyObject::GetValue<int>(ValueProperty);
    }

    void SetValue(int value)
    {
        DependencyObject::SetValue<int>(ValueProperty, value);
    }

    static const DependencyProperty* ValueProperty;

private:
    void InitializeComponent()
    {
        GUI::LoadComponent(this, "NumericUpDown.xaml");
    }

    bool ConnectEvent(BaseComponent* source, const char* event, const char* handler) override
    {
        NS_CONNECT_EVENT(Button, Click, UpButton_Click);
        NS_CONNECT_EVENT(Button, Click, DownButton_Click);
        return false;
    }

    void UpButton_Click(BaseComponent*, const Noesis::RoutedEventArgs&)
    {
        int step = DependencyObject::GetValue<int>(StepValueProperty);
        SetValue(GetValue() + step);
    }

    void DownButton_Click(BaseComponent*, const Noesis::RoutedEventArgs&)
    {
        int step = DependencyObject::GetValue<int>(StepValueProperty);
        SetValue(GetValue() - step);
    }

    NS_IMPLEMENT_INLINE_REFLECTION(NumericUpDown, UserControl)
    {
        NsMeta<TypeId>("UserControl.NumericUpDown");

        UIElementData* data = NsMeta<UIElementData>(TypeOf<SelfClass>());
        data->RegisterProperty<int>(ValueProperty, "Value",
            FrameworkPropertyMetadata::Create(int(0), FrameworkPropertyMetadataOptions_None));
    }
};

const DependencyProperty* NumericUpDown::ValueProperty;

要将事件与代码隐藏函数连接,我们将覆盖ConnectEvent虚拟函数。对于关联的xaml中引用的每个事件,都会调用此函数,并提供事件源以及预期要调用的函数的名称。

大事记

下一步是公开一个事件,以在数字微调器的值更改时通知用户。我们将此事件称为ValueChanged,并将其实现为路由事件。路由事件必须使用公共静态成员和引发事件的虚函数声明,以便继承者可以覆盖基本实现:

NumericUpDown.xaml.cpp

class NumericUpDown: public UserControl
{
public:
    NumericUpDown()
    {
        InitializeComponent();
    }

    int GetValue() const
    {
        return DependencyObject::GetValue<int>(ValueProperty);
    }

    void SetValue(int value)
    {
        DependencyObject::SetValue<int>(ValueProperty, value);
    }

    /// Occurs when numeric value changes
    RoutedEvent_<RoutedPropertyChangedEventHandler<int>::Handler> ValueChanged()
    {
        return RoutedEvent_<RoutedPropertyChangedEventHandler<int>::Handler>(this, ValueChangedEvent);
    }

    static const DependencyProperty* ValueProperty;
    static const RoutedEvent* ValueChangedEvent;

protected:
    virtual void OnValueChanged(const RoutedPropertyChangedEventArgs<int>& args)
    {
        RaiseEvent(args);
    }

private:
    void InitializeComponent()
    {
        GUI::LoadComponent(this, "NumericUpDown.xaml");
    }

    bool ConnectEvent(BaseComponent* source, const char* event, const char* handler) override
    {
        NS_CONNECT_EVENT(Button, Click, UpButton_Click);
        NS_CONNECT_EVENT(Button, Click, DownButton_Click);
        return false;
    }

    void UpButton_Click(BaseComponent*, const Noesis::RoutedEventArgs&)
    {
        int step = DependencyObject::GetValue<int>(StepValueProperty);
        SetValue(GetValue() + step);
    }

    void DownButton_Click(BaseComponent*, const Noesis::RoutedEventArgs&)
    {
        int step = DependencyObject::GetValue<int>(StepValueProperty);
        SetValue(GetValue() - step);
    }

    static void OnValueChangedStatic(DependencyObject* d,
        const DependencyPropertyChangedEventArgs& e)
    {
        NumericUpDown* control = static_cast<NumericUpDown*>(d);
        int oldValue = *static_cast<const int*>(args.oldValue);
        int newValue = *static_cast<const int*>(args.newValue);

        RoutedPropertyChangedEventArgs<int> e(control, ValueChangedEvent, oldValue, newValue);
        control->OnValueChanged(e);
    }

    NS_IMPLEMENT_INLINE_REFLECTION(NumericUpDown, UserControl)
    {
        NsMeta<TypeId>("UserControl.NumericUpDown");

        Ptr<UIElementData> data = NsMeta<UIElementData>(TypeOf<SelfClass>());
        data->RegisterProperty<int>(ValueProperty, "Value",
            FrameworkPropertyMetadata::Create(int(0), &OnValueChangedStatic));

        data->RegisterEvent(ValueChangedEvent, "ValueChanged", RoutingStrategy_Bubbling);
    }
};

const DependencyProperty* NumericUpDown::ValueProperty;
const RoutedEvent* NumericUpDown::ValueChangedEvent;

改进之处

本教程随附的源代码进行了一些改进。通过添加新的依赖项属性来控制最大值和最小值以及每当单击向上/向下按钮时的步进因子,我们扩展了微调器的功能。建议您仔细阅读代码。

用法

现在,我们可以在任何其他XAML文件中使用控件。例如,作为RGB颜色值的编辑器:

UserControlTutorialImg2.jpg
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  UseLayoutRounding="True">

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <GroupBox Header="BACKGROUND: " HorizontalAlignment="Center" Margin="0,20,0,0" Padding="10">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <Rectangle Stretch="Fill" Stroke="Black" Width="80">
                    <Rectangle.Fill>
                        <SolidColorBrush x:Name="BgColor" Color="White"/>
                    </Rectangle.Fill>
                </Rectangle>

                <Grid Grid.Column="1" Margin="10,0,4,0">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock Text="R:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="0"/>
                    <TextBlock Text="G:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="1"/>
                    <TextBlock Text="B:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="2"/>
                </Grid>

                <Grid Grid.Column="2" Width="60">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <NumericUpDown Grid.Row="0" Margin="0,0,0,0"/>
                    <NumericUpDown Grid.Row="1" Margin="0,2,0,0"/>
                    <NumericUpDown Grid.Row="2" Margin="0,2,0,0"/>
                </Grid>

            </Grid>
        </GroupBox>
        <GroupBox Header="FOREGROUND: " HorizontalAlignment="Center" Margin="20,20,0,0" Padding="10">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <Rectangle Stretch="Fill" Stroke="Black" Width="80">
                    <Rectangle.Fill>
                        <SolidColorBrush x:Name="FgColor" Color="Black"/>
                    </Rectangle.Fill>
                </Rectangle>

                <Grid Grid.Column="1" Margin="10,0,4,0">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock Text="R:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="0"/>
                    <TextBlock Text="G:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="1"/>
                    <TextBlock Text="B:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Row="2"/>
                </Grid>

                <Grid Grid.Column="2" Width="60">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <NumericUpDown Grid.Row="0" Margin="0,0,0,0"/>
                    <NumericUpDown Grid.Row="1" Margin="0,2,0,0"/>
                    <NumericUpDown Grid.Row="2" Margin="0,2,0,0"/>
                </Grid>

            </Grid>
        </GroupBox>
        <TextBlock Text="Sample Text" HorizontalAlignment="Center" Margin="20,30,0,0" FontSize="24" Padding="10,5"
             VerticalAlignment="Center" Background="{Binding ElementName=BgColor}"
             Foreground="{Binding ElementName=FgColor}"/>
    </StackPanel>
</Grid>
上一篇 下一篇

猜你喜欢

热点阅读