0

I try to create a custom ComboBox called MyComboBox. It has a button for switching between the previous and next items.

I store the Background color of the base in BaseBackground. This is useful, as I don't want the FrontGlyph to have the Background inherited from the templated parent.

This is my WPF code:

<Style x:Key="{x:Type local:MyComboBox}" TargetType="{x:Type local:MyComboBox}">
    </Style.Resources>
    <Setter Property="Focusable" Value="False" />
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="OverridesDefaultStyle" Value="True" />
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyComboBox}">
                <Grid Cursor="Hand">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />       <!-- A glyph will come here -->
                        <ColumnDefinition Width="*" />          <!-- The base combo box can take as much space as it can. -->
                    </Grid.ColumnDefinitions>
                    <Path x:Name="FrontGlyph" Grid.Column="0" Data="M 0.0 16.0 L 6.0 0.0 L 6.0 16.0 Z" Fill="{TemplateBinding BaseBackground}" Stretch="Fill" />
                    <Grid x:Name="BaseComboBox" Grid.Column="1" Background="{TemplateBinding BaseBackground}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />   <!-- Previous item -->
                            <ColumnDefinition Width="Auto" />   <!-- Next item -->
                            <ColumnDefinition Width="*" />      <!-- Content Presenter -->
                            <ColumnDefinition Width="Auto" />   <!-- Drop down button -->
                        </Grid.ColumnDefinitions>
                        <Button x:Name="Prev" Grid.Column="0" Background="{TemplateBinding Background}" Style="{StaticResource MyUIButton}">
                            <Path VerticalAlignment="Center" Data="M 4.5 0.5 L 0.5 4.5 L 4.5 8.5 Z" Fill="Black" />
                        </Button>
                        <Button x:Name="Next" Grid.Column="1" Background="{TemplateBinding Background}" Style="{StaticResource MyUIButton}">
                            <Path VerticalAlignment="Center" Data="M 0.5 0.5 L 4.5 4.5 L 0.5 8.5 Z" Fill="Black" />
                        </Button>
                        <ContentPresenter x:Name="ContentSite" Grid.Column="2" IsHitTestVisible="False" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" VerticalAlignment="Stretch" HorizontalAlignment="Left" />
                        <ToggleButton x:Name="ToggleButton" Template="{StaticResource ComboBoxToggleButton}" Grid.Column="3" Focusable="false" ClickMode="Press" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Background}" />
                        <Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
                            <Grid x:Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                <Border x:Name="DropDownBorder" BorderThickness="1" BorderBrush="{TemplateBinding Background, Converter={StaticResource LightenBrushColor}, ConverterParameter=0.5}" Background="{TemplateBinding Background}" />
                                <ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
                                    <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                                </ScrollViewer>
                            </Grid>
                        </Popup>
                    </Grid>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BaseBackground" Value="Goldenrod" />  <!-- This does not work -->
                        <!--<Setter TargetName="FrontGlyph" Property="Fill" Value="Goldenrod" />
                        <Setter TargetName="BaseComboBox" Property="Background" Value="{Binding Path=Fill, ElementName=FrontGlyph}" />-->   <!-- These 2 lines do work. -->
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

And this is the source code of MyComboBox:

class MyComboBox : ComboBox
{
    public static readonly DependencyProperty BaseBackgroundProperty;

    public SolidColorBrush BaseBackground { get { return (SolidColorBrush)GetValue(BaseBackgroundProperty); } set { SetValue(BaseBackgroundProperty, value); } }

    static MyComboBox()
    {
        BaseBackgroundProperty = DependencyProperty.Register("BaseBackground", typeof(SolidColorBrush), typeof(MyComboBox), new FrameworkPropertyMetadata(Brushes.Lime, FrameworkPropertyMetadataOptions.AffectsRender, OnBaseBackgroundPropertyChanged));
    }

    public MyComboBox()
    {
        DefaultStyleKey = typeof(MyComboBox);
    }

    private static void OnBaseBackgroundPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        // This is not called when the trigger tries to set BaseBackground when the mouse is over the control
    }
}

When I hover the mouse over the control, it should change colors. I should be able to do this via changing the BaseBackground, since both FrontGlyph and BaseComboBox have their background color bound to that. Nevertheless the code compiles but the change does not happen. Not just on the UI, but if I debug the code, I don't see the change in C# either.

If I change the background color of the FrontGlyph and bind the BaseComboBox.BackgroundColor to that, it works nicely.

Could someone explain why the changes of my custom property BaseBackground is not registered? It must not be the standard "Implement the INotifyPropertyChanged interface." issue, as I use Brush as my property and Brushes work pretty well everywhere else. :)

My implementation might look silly. Well, I'm kinda new to WPF. Plus I didn't want to burden you with the whole implementation, just tried to replicate the critical parts.

UPDATE

I've found out that in my source code I set BaseBackground = new SolidColorBrush(...) if some conditions are fulfilled. If I remove this line of code, now the triggers work and BaseBackground gets assigned the Goldenrod color.

But I wonder, why changing a DependencyProperty from C# code prevents it working from XAML markup. Besides, I need both of them to work.

Thank you.

Shakaron
  • 1,027
  • 10
  • 24
  • Your dependency property is typed as `SolidColorBrush` instead of `Brush`. It might not matter, but it is possible some type checking is happening somewhere and deciding the property cannot be set. Try typing the dependency property and the associated clr property as `Brush` and see if anything improves. Also, setting "AffectsRender" in the property metadata is going to cause unnecessary arrange invalidations. Since you do not override `ArrangeOverride` or `OnRender` in your code behind, there is no reason for any of your custom properties to cause invalidation. – Xavier May 28 '15 at 19:31
  • @Xavier thanks for the suggestions. I've changed the clr property from `SolidColorBrush` to `Brush`, but it did not help though. I'll have a look at the source of .NET. I should have thought of it myself, as one of my colleague just showed me not too long ago, that Microsoft made .NET open source. – Shakaron May 29 '15 at 09:45
  • I've added an update that might shed some more light on what's going on (or raise ever more questions). – Shakaron May 29 '15 at 10:50
  • Sorry Shakaron, my last two comments were in response to @clemens comment, but I forgot to address them to him. Also, he has deleted his comment, so I am deleting my responses to hopefully clear up any confusion for people reading these. – Xavier May 29 '15 at 11:38
  • 1
    **I think the problem is that I don't have a solid understanding of WPF and binding. I see the problem is that I have my default Background as a Binding. Later I override that with a simple Brush, but that makes me lose the binding. I think that's why things stop working.** – Shakaron May 29 '15 at 15:30
  • Yes, it was my mistake. I overrode the markup binding by directly setting the value from code. – Shakaron Jun 05 '15 at 14:29

2 Answers2

1

Summary

I think what you probably want to do is register the BaseBackgroundProperty under the name "BaseBackground" (rather than "Background") since your xaml is trying to set a property with that name.

Details

Looking at your code, I think the reason your MyComboBox.BaseBackground property is never set is because it is being registered with the dependency property system using the name "Background". This means you have a property registered in a base class as "Background" (Control.BackgroundProperty), and a property registered in your derived class as "Background" (MyComboBox.BaseBackgroundProperty). So in theory, setting "Background" in xaml should end up setting MyComboBox.BaseBackgroundProperty while setting "Control.Background" should end up setting the Control.Background property.

While that works in theory, I have no idea if it works in practice. It is also not really the way to go about doing things. You could override the metadata for Control.BackgroundProperty in your type initializer (aka static constructor) if you wanted to modify the existing property for your class in some way, but I don't think that is what your intention is here. Your xaml is trying to set the non-existent property named "BaseBackground".

Xavier
  • 3,254
  • 15
  • 22
  • You would be totally right if I'd register my *BaseBackgroundProperty* as "Background", but my code says `BaseBackgroundProperty = DependencyProperty.Register("BaseBackground", typeof(SolidColorBrush), typeof(MyComboBox), new FrameworkPropertyMetadata(Brushes.Lime, FrameworkPropertyMetadataOptions.AffectsRender, OnBaseBackgroundPropertyChanged));` i.e. I register it under the name *BaseBackground*. I've added an update that tells that my `BaseBackground` property works fine if I try to set it only from XAML markup and never from C# code behind. So I'm facing a different kind of problem now. :) – Shakaron May 29 '15 at 12:41
  • Sorry, I must have misread your code. I really thought I remembered seeing it register using the name "Background" when I read it earlier for some reason. – Xavier May 29 '15 at 14:45
  • For the record, I changed the question a couple of times. But the original version was correct in this matter too. No worries, it is still a good advice, not to hide properties. – Shakaron May 29 '15 at 15:28
1

Based on your question update, it looks like what you are doing is setting a local value on the dependency property, which might be breaking the template binding. This can happen with one way bindings in certain situations where the target gets set by some other means. It seems like you are setting the source in this case, but it is possible that it is causing the binding to break.

You can debug the binding by setting the PresentationTraceSources.TraceLevel property on it to "High". (You might need to use the standard {Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay} syntax rather than the {TemplateBinding} shortcut syntax in order to attach the property – I am not sure.) Then, run the application and look at the debug output. It will tell you if the binding gets broken.

If the binding is in fact being broken, there are different things you can do to fix it, depending on your use cases.

  1. You can set the binding mode to TwoWay to keep it from breaking.
  2. You can try only setting it from code using SetCurrentValue instead of SetValue, as many controls tend to do when modifying their own dependency properties from code.

Potentially related information:

Community
  • 1
  • 1
Xavier
  • 3,254
  • 15
  • 22
  • just for closure: I realized what the problem was. I've added a comment with bold to the original question. Thank you for the help, they have been useful, however the root cause of my problem was my own ignorance. – Shakaron Jun 05 '15 at 14:32