背景色遮挡了WPF UserControl内容。

3

我有一个UserControl,在XAML中引用方式如下:

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

这个UserControl有几个矩形形状,它们正常显示。

但是,如果我指定一个Background颜色,指定的颜色会遮挡矩形,只会显示颜色。例如:

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

如果我将颜色更改为“透明”,那么矩形确实变得可见。

我还尝试为UserControl(作为Style的一部分)使用ControlTemplate,但结果相同(即背景色阻挡了UserControl的内容)。

我查看了MSDN上的Control.Background属性,该属性提供以下说明:

Background属性仅适用于控件的静止状态。控件的默认样式指定其状态更改时的外观。例如,如果您在按钮上设置Background属性,则仅当按钮未被按下或禁用时才具有该值。如果要创建具有更高级别的背景自定义的控件,则必须定义控件的样式。

此属性仅影响使用Background属性作为参数的模板的控件。对于其他控件,此属性没有影响。

MSDN中的备注有什么意义,我如何指定背景颜色而不阻止控件内容?

编辑:如果手动在代码后台添加内容控件(矩形),是否会有所不同。

UserControl代码:

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++;
                }
            }
        }
    }
}

1
你能发布你的UserControl代码吗?我按照你的描述尝试自己创建了一个,但是无法复现问题。 - Tzah Mama
是的,我也有同样的经历。在我的几个项目中,我已经做过很多次了,因为Usercontrol的背景总是在它的子元素的底部,所以内容总是可见的。 - Robert Snyder
刚看到你的编辑。是的,那可能会有所不同。如果你正在绘制用户控件,我的建议是在你的用户控件上放置一个画布并在其上绘制。 - Robert Snyder
@TzahMama 已添加了控件的代码。 - Sabuncu
@RobertSnyder 我会尝试的。 - Sabuncu
在代码后端尝试了以下内容,但得到了相同的结果:this.Background = Brushes.Blue; - Sabuncu
3个回答

5
为了编写一个通过重写 OnRender 方法进行渲染的自定义控件,您不应该从UserControlControl派生,因为它们通过ControlTemplate绘制自身,并使用Background刷子填充它们的区域。所有这些都在它们的OnRender方法之外完成,因此覆盖它并不调用基类的OnRender方法是不起作用的。
相反,应该从FrameworkElementUIElement派生,声明一个Background属性,并在进行其余渲染之前用背景填充控件区域:
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...
    }
}

您可以在MSDN上的控件编写概述文章中找到更多有关相关信息,其中有一个有关从FrameworkElement派生的部分。


+1 WORKS!谢谢你们的回答和教育。由于FrameworkElement不支持OnMouseDoubleClick(),我会找到一个解决方法。另外一个问题:为什么不调用base.OnRender()也可以? - Sabuncu
基本实现什么也不做。然而,调用它并不会有任何损失。这是一个好的实践方式。 - Clemens

1
如果您使用Visual Studio的模板创建自定义控件,它会创建一个Generic.xaml文件,为该控件提供ControlTemplate。
该模板中的默认设置会将Border的背景从ControlTemplate复制过来,导致背景(不知何故渲染在自定义控件之上)覆盖了自定义控件。
<ControlTemplate TargetType="{x:Type local:MyControl}">
    <Border Background="{TemplateBinding Background}">

为了解决这个问题,需要从ControlTemplate中移除Border Background,并在OnRender方法中首先绘制一个背景填充的矩形。请保留HTML标签。
drawingContext.DrawRectangle(Background, null, new Rect(RenderSize));

1

所以我的解决方案可行,但您需要调整宽度和高度。在用户控件中,我添加了一个视图框和一个均匀网格。

<UserControl x:Class="GraphingWithShapes.ColumnGraphRenderCtrl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Viewbox>
        <UniformGrid x:Name="GraphGrid" />
    </Viewbox>
</UserControl>

然后,我根据输入的数据设置了它的宽度和高度。使列数等于数据数量。(请注意,我没有像您那样添加更改图表颜色的逻辑)。然后,我添加了一个边框(其中包含边框和背景),并将其添加到统一的网格中。(代码在这里)
    public void SetData(ObservableCollection<NameValuePair> data)
    {
        _dataPoints = data;
        _dataPoints.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_dataPoints_CollectionChanged);
        GraphGrid.Columns = _dataPoints.Count;

        RebuildGraph();
        InvalidateVisual();
    }

    private void RebuildGraph()
    {
        GraphGrid.Children.Clear();
        GraphGrid.Height = GetLargestValue();
        GraphGrid.Width = _dataPoints.Count * 3;
        foreach (var item in _dataPoints)
        {
            AddGraphBar(item.Value);
        }
    }

    private void AddGraphBar(double value)
    {
        Border grid = new Border();
        grid.BorderBrush = Brushes.Black;
        grid.BorderThickness = new Thickness(1);
        grid.Background = Brushes.Green;
        grid.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
        grid.Height = value;
        GraphGrid.Children.Add(grid);
    }

当我在用户控件上设置背景颜色时,它现在可以工作了。希望这会有所帮助。


+1 谢谢。不过,我的代码使用 DrawRectangle() 调用而不是形状,所以我无法使用您的方法。 - Sabuncu
@Sabuncu 好的,用Canvas替换UniformGrid,并在Canvas上绘图。你不能在UserControl上绘图,因为背景会覆盖你在UserControl上绘制的内容。 - Robert Snyder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接