0

Problem

I've created a custom control (OmniBox), which has its base style set with:

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

But when I'm using my control, I want to be able to do something like:

<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"... />
           ...

And have all instances of my control take on that default margin. Unfortunately, my controls are not responding to the style set in the resources. They are just keeping their default margin of "0,2".

Strangely, if I explicitly set the margin on my controls like so:

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

They DO use the margin of "0,10" rather than "0,2". How come the template type isn't working?

If it's relevant, my OmniBox control templates all look like this:

<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>

First Attempt

In my grid style, I've tried setting Margin to

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

But it didn't help in sucking down the templated margin.

Second Attempt

I tried creating a custom margin dependency property and binding the grid to that:

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

My custom property was defined as:

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

Anyways it didn't work. The default margin set in the dependency property above is still overriding the margin I'm trying to set in the style template.

Alain
  • 26,663
  • 20
  • 114
  • 184

4 Answers4

3

You can add a default style for a custom control by overriding the metadata for the DefaultStyleKey:

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

You then create a resource dictionary called Generic.xaml that is located in a directory called Themes in the root of the project (so the path will be "/Themes/Generic.xaml"). In that resource dictionary you create a default style for your control:

<!-- 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>

If you just add a MyButton control it will get the default style, but you can override properties set in the default style by applying a new style:

<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>
Erik Öjebo
  • 10,821
  • 4
  • 54
  • 75
  • +1 Hey, sorry I didn't really see what you were getting at until I stumbled onto the solution I posted, but this was the right general idea - using OverrideMetaData - I just wanted it specifically for the margin property. I appreciate the help. – Alain Oct 14 '11 at 14:23
1

Have you overridden the DefaultStyleKey property in your OmniBox control?

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • From what I can tell, there is no DefaultStyle property on my control, I'm certainly not referencing anything like it. – Alain Oct 13 '11 at 19:40
  • Did a quick search through MSDN and I can't find any such thing except in something called the [BaseValueSource Enumeration](http://msdn.microsoft.com/en-us/library/system.windows.basevaluesource.aspx) which appears to have something to do with Dependency Properties :? – Alain Oct 13 '11 at 19:45
  • http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.defaultstylekey.aspx. Example is contained within. I should have said DefaultStyleKey rather than DefaultStyle. – Kent Boogaart Oct 13 '11 at 21:46
1

GridStyle specifies TargetType="Grid", so the setter <Setter Property="Margin" Value="0,2" /> applies to the Grid at the root of the control template. Setting the Margin property of the containing OmniBox has no effect of the margin of that grid.

Try specifying this in the template:

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

Notice I did not set the Style property as you did in the template. This is because the grid's Margin property will always reflect the Margin property of the OmniBox containing it, negating the effect of the Margin property in GridStyle. Instead you will want to default the OmniBox.Margin property and remove GridStyle entirely:

<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
    <Setter Property="Margin" Value="0 2" />
    <Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
Bryan Watts
  • 44,911
  • 16
  • 83
  • 88
  • Unfortunately, I can't do away with the GridStyle entirely, it's full of other configurations. Is this supposed to work regardless? I'll try what you said about moving the default margin to the Omnibox style rather than the Grid within. – Alain Oct 14 '11 at 00:26
  • @Alain: You just need to remove the `Margin` setter from `GridStyle`. I only said you should remove the style entirely because that was the only setter in it in your question. Everything else should work fine. – Bryan Watts Oct 14 '11 at 01:35
  • When I do the above I get the compile error "'Margin' member is not valid because it does not have a qualifying type name". What's that all about? – Alain Oct 14 '11 at 13:04
  • @Alain: I made the assumption that `OmniBox` has the `Margin` property already defined (i.e. that it ultimately derives from FrameworkElement: http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.margin.aspx). Please verify that the `Margin` property is defined on `OmniBox`; if not, please define it as you did in your question and try again. – Bryan Watts Oct 14 '11 at 13:24
  • Checked and it does have margin, inherits it from System.Windows.Controls.Control. If I re-define it, I get a warning that I'm hiding an inherited member, and it still doesn't work. – Alain Oct 14 '11 at 13:34
  • I have never seen this before and I have done what you are trying to do many times. Can you strip your solution down to only the elements that are relevant here, and see if you get the error? Also, can you post the `OmniBox` style? – Bryan Watts Oct 14 '11 at 14:06
  • +1 I approached from a different angle and figured out something I could do in the control class itself rather than the xaml of the control templates. I really appreciate your time. – Alain Oct 14 '11 at 14:24
0

After happening on this question, I figured out what I needed to do. In the control's class, I need to override the margin property's default value:

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

After that, I get rid of the margin on the "Grid" component of the omnibox completely, since the control itself carries a margin. Now when the user sets the "Margin" property on the OmniBox, it accepts it, if they don't, it uses the default value.

Thank you all so much for your suggestions and effort.

Community
  • 1
  • 1
Alain
  • 26,663
  • 20
  • 114
  • 184