.NET - Background color problem with WinForms and WPF
- Date:
- Author: Stefan Cruysberghs
A few days ago I was examining the interoperability between Windows Forms and Windows Presentation Foundation (WPF). It seems to be quite easy to integrate WPF user controls in a WinForms form. This can be done by creating an ElementHost (System.Windows.Forms.Integration) object which is a host for a WPF control. I really like this component because it makes it possible to integrate some of the powerful graphical features of WPF in current WinForms applications.
Naga Satish, a Microsoft .NET Framework Client Team member, has posted some interesting blog posts about this Crossbow project : http://blogs.msdn.com/nagasatish
Using an ElementHost object in design-time
I started by creating a new Windows Forms Application. Then I've added references to WindowsFormsIntegration.dll, PresentationCore.dll and PresentationFramework.dll.
After this I have added a new WPF User Control to my WinForms project. The XAML file of WPF user controls contains an Image, a TextBlock, a TextBox and a Button.
<UserControl x:Class="CrossBowApplication.WpfUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<Style x:Key="ShadowStyle">
<Setter Property="Control.BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect Color="Black" Direction="315"
ShadowDepth="3" Softness="0.1" Opacity="0.5"/>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Border x:Name="Photo" BorderBrush="DarkSlateBlue" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center" Height="100">
<Border BorderBrush="White" BorderThickness="2">
<Image Stretch="UniformToFill" HorizontalAlignment="Left"
Source="D:\Photos\Karibu.jpg">
</Image>
</Border>
</Border>
<Border Height="60">
<Border.Background>
<VisualBrush Visual="{Binding ElementName=Photo}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX="0" CenterY="30">
</ScaleTransform>
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="Black"></GradientStop>
<GradientStop Offset="0.9" Color="Transparent"></GradientStop>
</LinearGradientBrush>
</Border.OpacityMask>
</Border>
</StackPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" TextAlignment="Center" Style="{StaticResource ShadowStyle}"
Text="Karibu" FontSize="26" Foreground="DarkSlateBlue" FontWeight="Bold" Margin="4" >
</TextBlock>
<TextBox Grid.Row="1" Name="textBox1" Margin="4" TextWrapping="Wrap">
Blackback mountain gorilla in Uganda. September 2007.
</TextBox>
<Button Grid.Row="2" Click="OnClick" Margin="4">Show more photos ...</Button>
</Grid>
</Grid>
</UserControl>
Finally I've dropped an ElementHost object on my WinForms form. This component can be found on the WPF Interoperability tab of the Toolbox. Just assign the Child property in design-time and the WPF control will become visible in your WinForms form. It really is that simple.
Creating an ElementHost object in run-time
Additionally, I've tried to create the ElementHost object and the WPF user control in run-time.
WpfUserControl wpfUsercontrol = new WpfUserControl();
ElementHost elementHost = new ElementHost();
elementHost.Child = wpfUsercontrol;
elementHost.Location = new System.Drawing.Point(225, 200);
elementHost.Size = new Size(300, 150);
this.Controls. Add(elementHost);
As you can see from the screenshot above this causes an annoying background color problem. It turns out that the background of the WPF user control becomes black. I tried to change the BackColor and BackColorTransparent properties of the ElementHost object but this didn't solve it. Setting BackColor to Color.Red or SystemColors.Highlight works fine, but setting it to SystemColors.Control will turn the background into black. Normally this should work because the ElementHost class has several mapped properties like color, font, cursor, ...
To solve the problem, I assigned a new SolidColorBrush object to the Background property of my WPF user control. Therefore I needed to convert a System.Drawing.Color (WinForms) to a System.Windows.Media.Color (WPF).
Color color = SystemColors.Control;
wpfUsercontrol.Background = new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromRgb(color.R, color.G, color.B));
Mapping properties
This works fine but I was looking for a cleaner and better solution. After some Googling I found an interesting walkthrough on the MSDN website about mapping properties : http://msdn2.microsoft.com/en-us/library/ms788740.aspx
The PropertyMap property of the ElementHost class can be used to map Windows Forms properties to corresponding properties on the hosted WPF element. There are already several default mappings for color, font, cursor, ... but it is also possible to remove them or to add new mappings. The cool thing about adding your own property mappings is that you can add your own logic on how to update the WPF control when properties of the ElementHost change.
So my final solution is to map the BackColor property of the ElementHost to my own event which will update the Background property of the WPF user control. This should be a good workaround for the color mapping problem of the ElementHost. Now the background of my WPF control will be displayed in the same SystemColors.Control color as my WinForms form.
private void buttonCreateAdvUserControl_Click(object sender, EventArgs e)
{
WpfUserControl wpfUsercontrol = new WpfUserControl();
ElementHost elementHost = new ElementHost();
elementHost.Child = wpfUsercontrol;
elementHost.Location = new System.Drawing.Point(225, 200);
elementHost.Size = new Size(300, 150);
if (elementHost.PropertyMap["BackColor"] != null)
{
elementHost.PropertyMap["BackColor"] += new PropertyTranslator(OnBackColorChange);
}
this.Controls.Add(elementHost);
}
private void OnBackColorChange(object sender, String propertyName, object value)
{
ElementHost elementHost = sender as ElementHost;
WpfUserControl wpfUsercontrol = elementHost.Child as WpfUserControl;
System.Windows.Media.SolidColorBrush brush =
new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(
((System.Drawing.Color)value).R, ((System.Drawing.Color)value).G, ((System.Drawing.Color)value).B));
wpfUsercontrol.Background = brush;
}
The next step could be to create your own class which inherits from the ElementHost class. I will leave this for you to implement.