Fixing WPF ComboBox Colors Without Extending or Templating






2.33/5 (2 votes)
This article demonstrates a very simple technique which can be used to easliy modify the standard Windows Presentation Foundation controls without creating an extended class or using a data template.
Introduction
If you have worked with WPF and used a non-standard color scheme, you may have run into a problem with ComboBoxes or other WPF controls. I was using a dark color scheme with White text. However, the ComboBoxes in my application ignored the foreground setting in the style. The only way I could get the ComboBox foreground to change was to explicitly set it for each ComboBox. ComboBox styling in WPF was broken.
I tried different techniques to fix the problem, which all required more code than I liked:
- First attempt: Extend the
ComboBox
class with my own class that hides the foreground properties and set them manually. This worked, but it required me to use a different class name everywhere in my XAML code and in the style'sTargetType
. - Second attempt: Create a data template that contained a
ComboBox
and set the colors manually. This did not work, as the data template's foreground was never set. - Final attempt: Listen to the
Foreground
Dependency Property'sChanged
event (actually a callback) and fix the colors there. This worked! And it required no changes to any ComboBox declarations.
Example
The first two combo boxes have not changed the foreground color, while the second set are correct.
The background color of the drop down is still White.
The background color of the drop down matches the background color.
Using the Code
Here is the important class that does all the work:
public static class ComboBoxFix
{
private static bool _isInitialized = false;
/// <summary>
/// Initialize must be called
/// before any Combo boxes are created
/// </summary>
public static void Initialize()
{
if( !_isInitialized )
{
// Registed the callback methods
// when the properties change on a ComboBox class
ComboBox.BackgroundProperty.OverrideMetadata(
typeof( ComboBox ),
new FrameworkPropertyMetadata( OnBackgroundChanged ) );
ComboBox.ForegroundProperty.OverrideMetadata(
typeof( ComboBox ),
new FrameworkPropertyMetadata( OnForegroundChanged ) );
_isInitialized = true;
}
}
private static void OnBackgroundChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e )
{
// Set the drop down background color to match the background
SetDropDownBackground( d as ComboBox );
}
private static void OnForegroundChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e )
{
// Manually set the foreground (to overcome bug)
// Apparently the ComboBox does not listen
// when the Foreground DepencyProperty
// is changed and therefore does not
// update itself unless the value is changed
// through the Foreground .net property
(d as ComboBox).Foreground = e.NewValue as Brush;
}
private static void SetDropDownBackground( ComboBox comboBox )
{
// The drop down control uses
// the WindowBrush to paint its background
// By overriding that Brush (just for this control)
if( comboBox.Resources.Contains( SystemColors.WindowBrushKey ) )
{
comboBox.Resources.Remove( SystemColors.WindowBrushKey );
}
comboBox.Resources.Add(
SystemColors.WindowBrushKey, comboBox.Background );
}
}
The Intialize
method must be called before any ComboBox
es are created. The best place to put the ComboBoxFix.Iniatilize()
call is on the first line of the main window's constructor.
Points of Interest
The Dependency Property system is very powerful. With very little code, it is possible to register to the Changed
events of all objects of a specific type that contain that property.
However, it is more complex than a standard .NET property. It is important to remember that WPF binding, animations, etc., set the Dependency Property directly. They do not go through the .NET property setter.
It seems that the source of this problem is related to this difference.
Therefore, if you create any Dependency Properties, remember to handle the changes to that value by registering a callback method in the property metadata.