WPF派生控制但保持风格

时间:2017-06-21 15:43:06

标签: c# wpf

我正在开发一个应用程序,其中我有很多带有标签的滑块,如下所示:

Labeled Slider

为了重复使用,我想创建一个自定义LabeledSlider控件,其中包含左右标签字符串的额外属性。一个简单的UserControl起初看起来很合适,直到我意识到我必须重新实现我计划使用的所有Slider属性和方法(其中有很多)。 / p>

所以从Slider继承似乎是更好的选择,但看起来我必须创建一个自定义样式才能这样做。有没有办法从Slider派生并在保留现有样式的同时添加这些标签?

对于它的价值,我正在使用MahApps.Metro

1 个答案:

答案 0 :(得分:4)

如果您想避免使用UserControl实现自定义滑块所带来的所有管道,您可以直接使用Slider控件和自定义控件模板。


控制模板

自定义控件模板将定义显示自定义滑块所需的所有UI元素;像两个标签一样,"内部"滑块控件(UI中显示的实际滑块)以及任何其他所需/必需的UI元素。

这种控制模板的简单形式如何显示(不完整):

    <Slider
        TickPlacement="Both"
        TickFrequency="0.5"
        BorderBrush="Red"
        BorderThickness="3" >
        <Slider.Template>

            <ControlTemplate TargetType="Slider">
                <Border
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>

                        <TextBlock Text="Left label" Grid.Column="0" Grid.Row="0" />
                        <TextBlock Text="Right label" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
                        <Slider Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1"
                            BorderThickness="0"
                            Value="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            TickFrequency="{TemplateBinding TickFrequency}
                            TickPlacement="{TemplateBinding TickPlacement}"
                            ...
                        />
                    </Grid>
                </Border>
            </ControlTemplate>

        </Slider.Template>
    </Slider>

请注意&#34;外部&#34;的所有属性滑块应该传递到&#34;内部&#34;滑块应该具有相应的TemplateBindings用于&#34;内部&#34;滑块(如 Value TickFrequency TickPlacement 属性所示)。

注意 Value 属性的模板绑定。对于任何需要双向的模板绑定,速记形式{TemplateName SourceProperty}不会单向执行。对于双向模板绑定,绑定应声明为{Binding SourceProperty, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}

&#34;外部&#34;的一些属性。滑块,你可能不希望通过&#34;内部&#34;滑块。也许您希望边框(滑块控件支持边框)将滑块与文本标签一起环绕。对于这种情况,我上面的示例控件模板具有围绕&#34;内部&#34;滑块和文本标签。请注意&#34;外部&#34;与边界相关的属性。使用控件模板的滑块控件正在模板绑定到此Border元素,而不是&#34; inner&#34;滑块。

但是,边界的处理方式仍然存在问题。如果您(或其他人)在某个时间定义了包含边框的滑块的默认样式,则此默认样式的相应边框参数也将应用于&#34;内部&#34;滑块 - 一些不受欢迎的东西。为防止这种情况发生,我的示例控件模板为&#34; inner&#34;显式设置 BorderThickness 的值。滑块。

对于控件模板中UI元素的任何其他属性,以类似的方式执行此操作,您不希望它们各自的默认样式受到影响。


标签文本的附加属性

如果您希望能够通过绑定更改标签的文本,则需要为它们引入一些属性。实现此目的的一种方法是将它们实现为附加属性。它们的实现可以相当简单:

public static class SliderExtensions
{
    public static string GetLeftLabel(DependencyObject obj)
    {
        return (string)obj.GetValue(LeftLabelProperty);
    }

    public static void SetLeftLabel(DependencyObject obj, string value)
    {
        obj.SetValue(LeftLabelProperty, value);
    }

    public static readonly DependencyProperty LeftLabelProperty = DependencyProperty.RegisterAttached(
        "LeftLabel",
        typeof(string),
        typeof(SliderExtensions)
    );


    public static string GetRightLabel(DependencyObject obj)
    {
        return (string)obj.GetValue(RightLabelProperty);
    }

    public static void SetRightLabel(DependencyObject obj, string value)
    {
        obj.SetValue(RightLabelProperty, value);
    }

    public static readonly DependencyProperty RightLabelProperty = DependencyProperty.RegisterAttached(
        "RightLabel",
        typeof(string),
        typeof(SliderExtensions)
    );
}

上面的代码提供了两个附加属性(类型为string),称为&#34; SliderExtensions.LeftLabel&#34;和&#34; SliderExtensions.RightLabel&#34;。然后你可以按如下方式使用它们:

    <Slider
        local:SliderExtensions.LeftLabel="Port"
        local:SliderExtensions.RightLabel="Starboard"
        TickPlacement="Both"
        TickFrequency="0.5"
        BorderBrush="Red"
        BorderThickness="3" >
        <Slider.Template>
            <ControlTemplate TargetType="Slider">
                ...
                <TextBlock Text="{TemplateBinding local:SliderExtensions.LeftLabel}" Grid.Column="0" Grid.Row="0" />
                <TextBlock Text="{TemplateBinding local:SliderExtensions.RightLabel}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
                 ...
             ...


具有标签文本

的依赖项属性的自定义滑块类

如果您不想使用附加属性,您还可以从标准Slider类派生自己的自定义滑块控件,并将 LeftLabel RightLabel 实现为此自定义滑块类的依赖项属性。

public class MyCustomSlider : Slider
{
    public string LeftLabel
    {
        get { return (string)GetValue(LeftLabelProperty); }
        set { SetValue(LeftLabelProperty, value); }
    }

    public static readonly DependencyProperty LeftLabelProperty = DependencyProperty.Register(
        nameof(LeftLabel),
        typeof(string),
        typeof(MyCustomSlider)
    );


    public string RightLabel
    {
        get { return (string)GetValue(RightLabelProperty); }
        set { SetValue(RightLabelProperty, value); }
    }

    public static readonly DependencyProperty RightLabelProperty = DependencyProperty.Register(
        nameof(RightLabel),
        typeof(string),
        typeof(MyCustomSlider)
    );
}

此方法的另一个好处是,您可以专门为MyCustomSlider类型定义默认样式。有关该主题的更多信息,请参阅this answer

如果您愿意这样做,请不要忘记调整控件模板的目标类型:

    <local:MyCustomSlider
        LeftLabel="Port"
        RightLabel="Starboard"
        TickPlacement="Both"
        TickFrequency="0.5"
        BorderBrush="Red"
        BorderThickness="3" >
        <local:MyCustomSlider.Template>
            <ControlTemplate TargetType="TargetType="local:MyCustomSlider"">
                ...
                <TextBlock Text="{TemplateBinding LeftLabel}" Grid.Column="0" Grid.Row="0" />
                <TextBlock Text="{TemplateBinding RightLabel}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
                 ...
             ...

您不必强制使用字符串属性和TextBlocks作为标签。您可以使用ContentPresenter代替TextBlock,并创建object类型的 LeftLabel RightLabel 属性。这样你仍然可以使用文本标签,但是 - 如果需要 - 你也可以使用任何其他内容(例如图像)。

附注:我的答案基于标准的WPF滑块控件。如果你使用MahApps.Metro提供的滑块控件(我只知道很少),某些细节 - 例如命名,类型,属性的存在或不存在 - 可能与我的答案显示的不同。