【问题标题】:WPF Derive Control but Keep StyleWPF 派生控制但保持风格
【发布时间】:2017-11-24 15:05:37
【问题描述】:

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

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

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

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

【问题讨论】:

  • 您可以为滑块创建自定义 ControlTemplate,而不是创建自定义控件。对于左/右标签,您可以为 Slider 类型创建两个附加属性,这将提供左/右标签内容。这样,滑块仍然是沼泽标准滑块类,但您可以为其提供自定义视图 + 属性。或者,您可以简单地将滑块控件子类化并将属性添加到子类化的 Slider 类中,而不是自定义附加属性。
  • 附加属性方法很有趣,但我认为子类化Slider 是这里更好的方法。我将如何编写不需要重新设置整个滑块样式的 ControlTemplate
  • 为什么您认为您需要重新设置滑块的样式只是因为您要制作自己的控件模板?通常,制作您自己的控件模板不会强迫您重新设计任何东西,除非您有意重新设计某些东西。我想我缺少一条信息来完全了解您的情况......
  • 不写自定义ControlTemplate替换控件的整个可视化树吗?在这种情况下,我想我可以在ControlTemplate 中为我的自定义Slider 使用Slider,并以某种方式将它的所有属性绑定到“外部”Slider。我的头好痛……
  • 是的。所以,我猜你不是在谈论样式,而是在谈论模板将模板中滑块的属性绑定到使用控件模板的(外部)滑块控件的属性。我猜对了吗?

标签: c# wpf


【解决方案1】:

如果您想避免使用 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>

请注意,应该传递给“内部”滑块的“外部”滑块的所有属性都应该具有对应于“内部”滑块的 TemplateBindings(如 Value 所示,TickFrequency 和 TickPlacement 属性)。

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

您可能不希望通过“内部”滑块传递“外部”滑块的某些属性。也许您希望边框(滑块控件支持边框)与文本标签一起围绕滑块。对于这种情况,我上面的示例控件模板具有围绕“内部”滑块和文本标签的 Border 元素。请注意,使用控件模板的“外部”滑块控件中与边框相关的属性被模板绑定到此 Border 元素,而不是“内部”滑块。

但是,如何处理边框仍然存在问题。如果您(或其他人)有时为包含边框的滑块定义了默认样式,则此默认样式的相应边框参数也将应用于“内部”滑块——这是不受欢迎的。为了防止这种情况发生,我的示例控件模板为“内部”滑块显式设置了 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),称为“SliderExtensions.LeftLabel”和“SliderExtensions.RightLabel”。然后,您可以按如下方式使用它们:

    <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 类派生自己的自定义滑块控件,并将 LeftLabelRightLabel 实现为依赖属性这个自定义滑块类。

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 类型的LeftLabelRightLabel 属性。这样您仍然可以使用文本标签,但如果需要的话,您也可以使用任何其他内容(例如图像)。


旁注:我的回答是基于标准的 WPF 滑块控件。如果您使用的是 MahApps.Metro 提供的滑块控件(我对此知之甚少),某些细节(例如名称、类型、属性的存在或不存在)可能与我的回答显示的不同。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多