Sabuncu Sabuncu - 1 year ago 146
C# Question

Background color obstructs WPF UserControl content

I have a

UserControl
, which is referenced within XAML as follows:

<local:ColumnGraphRenderCtrl x:Name="graphCtrl" Grid.Column="1"
Height="Auto" Width="Auto"/>


The
UserControl
in question has several rectangle shapes, and they display fine.

However, if I specify a
Background
color, the specified color obstructs the rectangles, and nothing but the color is displayed. For example:

<local:ColumnGraphRenderCtrl x:Name="graphCtrl" Background="Blue" Grid.Column="1"
Height="Auto" Width="Auto"/>


(If I change the color to "Transparent", the rectangles do become visible.)

I also tried using a
ControlTemplate
for the
UserControl
(as part of a
Style
), but I got the same results (i.e. the background color blocking the content of the UserControl).

I looked up the Control.Background property on MSDN which provides the following remarks:


The Background property applies only to the resting state of a
control. The default style of the control specifies its appearance
when the state of the control changes. For example, if you set the
Background property on a Button, the button has that value only when
it is not pressed or disabled. If you want to create a control that
has a more advanced customization of the background, you must define
the control's style.

This property only affects a control whose template uses the
Background property as a parameter. On other controls, this property
has no impact.


What is the significance of the remarks in MSDN, and how can I specify a background color without it blocking control content?

EDIT: The content control (the rectangles) are added manually in the codebehind, if that makes a difference.

UserControl code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace GraphingWithShapes
{
public partial class ColumnGraphRenderCtrl: UserControl
{
private ObservableCollection<NameValuePair> _dataPoints = null;
private List<Color> _columnColors = new List<Color>() { Colors.Blue, Colors.Red, Colors.Green };

public ColumnGraphRenderCtrl()
{
InitializeComponent();
}

public void SetData(ObservableCollection<NameValuePair> data)
{
_dataPoints = data;
_dataPoints.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_dataPoints_CollectionChanged);
InvalidateVisual();
}

void _dataPoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
InvalidateVisual();
}

public double GetLargestValue()
{
double value = 0;

foreach (NameValuePair nvp in _dataPoints)
{
value = Math.Max(value, nvp.Value);
}

return value;
}

protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
}

protected override void OnRender(DrawingContext drawingContext)
{
if (_dataPoints != null)
{
double spaceToUseY = ActualHeight * 0.8;
double spaceToUseX = ActualWidth * 0.8;
double barWidth = spaceToUseX / _dataPoints.Count;
double largestValue = GetLargestValue();
double unitHeight = spaceToUseY / largestValue;

double bottom = ActualHeight * 0.9;
double left = ActualWidth * 0.1;

Brush fillBrush;
Pen outlinePen = new Pen(Brushes.Black, 1);
int nIndex = 0;
Rect rect;
double height;

foreach (NameValuePair nvp in _dataPoints)
{
fillBrush = new SolidColorBrush(_columnColors[nIndex % _columnColors.Count]);

height = (nvp.Value * unitHeight);
rect = new Rect(left, bottom - height, barWidth, height);
drawingContext.DrawRectangle(fillBrush, outlinePen, rect);

left += rect.Width;
nIndex++;
}
}
}
}
}

Answer Source

In order to write a custom control that does its rendering by means of an overridden OnRender method you should not derive from UserControl or even Control, as they draw themselves via a ControlTemplate, which also fills their area with the Background brush. All that is done outside their OnRender method, so overriding it and not calling the base class' OnRender won't help.

Instead, derive from FrameworkElement or UIElement, declare a Background property and fill the control area with the background before doing the rest of the rendering:

public class CustomControl : FrameworkElement
{
    public static readonly DependencyProperty BackgroundProperty =
        Control.BackgroundProperty.AddOwner(typeof(CustomControl));

    public Brush Background
    {
        get { return (Brush)GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext); // just good practice
        drawingContext.DrawRectangle(Background, null, new Rect(RenderSize));

        // your rendering code goes here...
    }
}

You may find some more information in the Control Authoring Overview article on MSDN. There is a section about deriving from FrameworkElement.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download