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's TargetType
.
- 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's Changed
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;
public static void Initialize()
{
if( !_isInitialized )
{
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 )
{
SetDropDownBackground( d as ComboBox );
}
private static void OnForegroundChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e )
{
(d as ComboBox).Foreground = e.NewValue as Brush;
}
private static void SetDropDownBackground( ComboBox comboBox )
{
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.