【问题标题】:How to give my custom control an overridable default margin?如何给我的自定义控件一个可覆盖的默认边距?
【发布时间】:2011-12-07 05:59:53
【问题描述】:

问题

我创建了一个自定义控件 (OmniBox),它的基本样式设置为:

<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
    <Setter Property="Margin" Value="0,2" />
</Style>

但是当我使用我的控件时,我希望能够执行以下操作:

<UserControl.Resources>
    <Style TargetType="{x:Type ui:OmniBox}">
        <Setter Property="HorizontalAlignment" Value="Stretch"/>
        <Setter Property="Margin" Value="0,10"/> <!--Not Working?-->
    </Style>
</UserControl.Resources>
<Grid>
     <StackPanel>
           <ui:OmniBox x:Name="One"... />
           <ui:OmniBox x:Name="Two"... />
           ...

并让我的控件的所有实例都采用该默认边距。不幸的是,我的控件没有响应资源中设置的样式。他们只是保持默认的“0,2”边距。

奇怪的是,如果我像这样显式设置控件的边距:

           <ui:OmniBox x:Name="One" Margin="0,10" Style="OBDefaultStyle" ... />
           <ui:OmniBox x:Name="Two" Margin="0,10" ... />
           ...

他们确实使用“0,10”而不是“0,2”的边距。为什么模板类型不起作用?

如果相关,我的 OmniBox 控件模板都如下所示:

<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
    <Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
<ControlTemplate TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultTemplate">
    <Grid x:Name="PART_Grid" Style="{StaticResource GridStyle}">
        ... (Content)
    </Grid>
</ControlTemplate>

第一次尝试

在我的网格样式中,我尝试将 Margin 设置为

<Setter Property="Margin" 
        Value="{Binding Path=Margin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OmniBox}}}" />

但它没有帮助吸收模板边距。

第二次尝试

我尝试创建一个自定义边距依赖属性并将网格绑定到该属性:

<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
    <Setter Property="Margin" Value="{Binding Path=MyMargin, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>

我的自定义属性定义为:

public static readonly DependencyProperty MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2), new PropertyChangedCallback(OnMarginChanged)));

无论如何,它没有工作。上面依赖属性中设置的默认边距仍然覆盖了我试图在样式模板中设置的边距。

【问题讨论】:

    标签: c# wpf xaml .net-3.5


    【解决方案1】:

    您可以通过覆盖 DefaultStyleKey 的元数据来为自定义控件添加默认样式:

    public class MyButton : Button
    {
        static MyButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
        }
    }
    

    然后,您创建一个名为 Generic.xaml 的资源字典,该字典位于项目根目录中名为 Themes 的目录中(因此路径将为“/Themes/Generic.xaml”)。在该资源字典中,您可以为控件创建默认样式:

    <!-- Base the style on the default style of the base class, if you don't want to completely
         replace that style. If you do, remember to specify a new control template in your style as well -->
    <Style TargetType="SomeNamespace:MyButton" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Margin" Value="10" />    
    </Style>
    

    如果您只是添加一个MyButton 控件,它将获得默认样式,但您可以通过应用新样式来覆盖默认样式中设置的属性:

    <Window x:Class="SomeNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SomeNamespace="clr-namespace:SomeNamespace"
        Title="MainWindow" Height="350" Width="525">
    
        <Window.Resources>
            <Style TargetType="SomeNamespace:MyButton">
                <Setter Property="Margin" Value="20" />
            </Style>
        </Window.Resources>
    
        <Grid>
            <SomeNamespace:MyButton />
        </Grid>
    </Window>
    

    【讨论】:

    • +1 嘿,抱歉,直到我偶然发现我发布的解决方案,我才真正看到你的意思,但这是正确的一般想法 - 使用 OverrideMetaData - 我只是想要它专门用于边距属性。感谢您的帮助。
    【解决方案2】:

    GridStyle 指定TargetType="Grid",因此设置器&lt;Setter Property="Margin" Value="0,2" /&gt; 应用于控件模板根目录下的Grid。设置包含OmniBoxMargin 属性不会影响该网格的边距。

    尝试在模板中指定这个:

    <Grid x:Name="PART_Grid" Margin="{TemplateBinding Margin}">
    

    请注意,我没有像您在模板中那样设置 Style 属性。这是因为网格的Margin 属性将始终反映包含它的OmniBoxMargin 属性,从而抵消GridStyleMargin 属性的效果。相反,您需要默认 OmniBox.Margin 属性并完全删除 GridStyle

    <Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
        <Setter Property="Margin" Value="0 2" />
        <Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
    </Style>
    

    【讨论】:

    • 不幸的是,我不能完全取消 GridStyle,它充满了其他配置。这应该不管用吗?我会尝试你所说的将默认边距移动到多功能框样式而不是其中的网格。
    • @Alain:您只需从GridStyle 中删除Margin 设置器。我只是说您应该完全删除该样式,因为这是您问题中唯一的二传手。其他一切都应该可以正常工作。
    • 当我执行上述操作时,我得到编译错误“'Margin' 成员无效,因为它没有合格的类型名称”。这是怎么回事?
    • @Alain:我假设OmniBox 已经定义了Margin 属性(即它最终派生自FrameworkElement:msdn.microsoft.com/en-us/library/…)。请验证Margin 属性是否在OmniBox 上定义;如果不是,请按照您在问题中所做的定义,然后重试。
    • 已检查并且确实有边距,从 System.Windows.Controls.Control 继承。如果我重新定义它,我会收到一条警告说我正在隐藏一个继承的成员,但它仍然不起作用。
    【解决方案3】:

    您是否覆盖了OmniBox 控件中的DefaultStyleKey 属性?

    【讨论】:

    • 据我所知,我的控件上没有 DefaultStyle 属性,我当然没有引用类似的东西。
    • 通过 MSDN 进行了快速搜索,除了似乎与依赖属性有关的 BaseValueSource Enumeration 之外,我找不到任何此类内容:?
    • msdn.microsoft.com/en-us/library/…。示例包含在其中。我应该说 DefaultStyleKey 而不是 DefaultStyle。
    【解决方案4】:

    发生在this question 之后,我想出了我需要做什么。在控件的类中,我需要覆盖边距属性的默认值:

        static OmniBox()
        {
            MarginProperty.OverrideMetadata(typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2)));
        }
    

    之后,我完全去掉了多功能框“网格”组件上的边距,因为控件本身带有边距。现在,当用户在 OmniBox 上设置“Margin”属性时,它会接受它,如果不接受,它会使用默认值。

    非常感谢大家的建议和努力。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-21
      • 2015-08-02
      相关资源
      最近更新 更多