带有内部控件的WPF装饰器

26

我试图实现一种不寻常的 Adorner 用法。当鼠标悬停在 RichTextBox 上时,会在其上方出现一个 Adorner(见下图),允许您向 Adorner 中包含的 ListBox 添加字符串列表。这用于向所装饰元素中的“标签”(如 Flickr)添加标记。

adorner diagram

首先:这样做是否可能?

大多数 Adorner 的示例均显示如何重写 Adorner 的 OnRender 方法来执行诸如绘制形状之类的琐碎操作。我能够使用此方法来呈现一组矩形,从而创建 Adorner 的灰色边框,该边框还会根据 RichTextBox 的高度调整大小,因为在 Adorner 显示时将添加额外的文本行。

protected override void OnRender(DrawingContext drawingContext)
{
    SolidColorBrush grayBrush = new SolidColorBrush();
    grayBrush.Color = Color.FromRgb(153, 153, 153);

    // left
    drawingContext.DrawRectangle(grayBrush, null, new System.Windows.Rect(1, 1, 5, ActualHeight));
    // right
    drawingContext.DrawRectangle(grayBrush, null, new System.Windows.Rect(ActualWidth - 6, 1, 5, ActualHeight));
    //bottom
    drawingContext.DrawRectangle(grayBrush, null, new System.Windows.Rect(1, ActualHeight, ActualWidth - 2, 5));

    // for reasons unimportant to this example the top gray bar is rendered as part of the RichTextBox

}

然而,添加控件略微有些棘手。一般情况下,WPF的adorner需要在代码中添加子控件而不是XAML。使用DrawingContext adorner - possible to draw stackpanel?中描述的技术,我已经学会了如何在Adorner的初始化器中轻松地添加子控件(如TextBox)。

然而,问题在于如何在Adorner中放置这些控件。

如果我能够创建一个带有灰色背景并将其定位在Adorner底部的网格,那么我应该就可以继续进行了。 我会假设(希望)像根据添加标签更改大小的自动调整Adorner大小等事情都将自动发生。

简而言之,假设这是可能的,是否有人能推荐一种在Adorner内创建此较低的标记控件区域,并相对于Adorner底部定位它的方法(可能必须根据RichTextBox内容的大小调整大小)?


你不必亲自动手,可以看看我过去使用过的[这篇CodeProject文章](http://www.codeproject.com/Articles/54472/Defining-WPF-Adorners-in-XAML)。它允许您在XAML中定义装饰器、进行绑定等。 - Louis Kottmann
我实际上花了几个小时摆弄这种方法,试图想出一种实现上述位置的方法,但我认为CodeProject示例中的代码不够健壮,无法容纳我想要做的事情。例如,它会将我创建的任何adorner剪切到装饰元素的边界之内(非常奇怪,因为adorner的z-index应该始终在顶部)。我尝试测试将AdornedControl.AdornerContent下的元素的高度绑定到Adorned元素的ActualHeight,但它失败了(静默地)。 - Ryan Norbauer
是的,它会进行剪辑,我发现它是按照布局进行剪辑而不是装饰元素。至于你的问题,我会再看一下。 - Louis Kottmann
谢谢你,Baboon。:) 我今天下午会继续努力,并报告我学到的任何东西。 - Ryan Norbauer
它实际上剪裁到了装饰层的边界。您可以在VisualTree中的任何位置放置AdornerDecorator,当您请求装饰层时,它就是获取该层的树的一部分。如果您的AdornerDecorator的ClipToBounds设置为true,则会剪切到该矩形。如果您不想剪切,请将AdornerDecorator放置在窗口级别,并且您的装饰器将自由地放置。 (我需要完全相反的情况...明确将AdornerDecorator放置在我希望子控件的装饰器被剪切的面板周围。) - Mark A. Donohoe
1个回答

52

哎呀!在 Ghenadie Tanasiev 的帮助下,我得到了一个答案。

与WPF中的大多数控件不同,装饰器没有任何内置的方法来分配子元素(比如我想添加的控件)。如果不添加任何内容到装饰器,你只能重写它们的OnRender方法并且在传递进去的DrawingContext中绘制一些东西。说实话,这可能适用于99%的装饰器使用情况(例如创建围绕对象的拖动手柄之类的东西),但是我需要向我的装饰器中添加一些真正的控件。

做到这一点的技巧是创建一个VisualCollection并将你的装饰器设为它的所有者,通过将其传递到集合的构造函数中。

这些都在这篇博客文章中有详细描述。不幸的是,直到我得知要搜索VisualCollection才能找到这篇文章,感谢Ghenadie的指导。

文章中没有提到,但请注意,在adorner的OnRender方法中绘制时可以结合使用VisualCollection技术。我正在使用OnRender来实现上图所述的侧边和顶部边框,并使用VisualCollection来放置和创建控件。

编辑:由于已经无法访问,这里是提到的博客文章的源代码:

public class AdornerContentPresenter : Adorner
{
  private VisualCollection _Visuals;
  private ContentPresenter _ContentPresenter;

  public AdornerContentPresenter(UIElement adornedElement)
    : base(adornedElement)
  {
    _Visuals = new VisualCollection(this);
    _ContentPresenter = new ContentPresenter();
    _Visuals.Add(_ContentPresenter);
  }

  public AdornerContentPresenter(UIElement adornedElement, Visual content)
    : this(adornedElement)
  { Content = content; }

  protected override Size MeasureOverride(Size constraint)
  {
    _ContentPresenter.Measure(constraint);
    return _ContentPresenter.DesiredSize;
  }

  protected override Size ArrangeOverride(Size finalSize)
  {
    _ContentPresenter.Arrange(new Rect(0, 0,
         finalSize.Width, finalSize.Height));
    return _ContentPresenter.RenderSize;
  }

  protected override Visual GetVisualChild(int index)
  { return _Visuals[index]; }

  protected override int VisualChildrenCount
  { get { return _Visuals.Count; } }

  public object Content
  {
    get { return _ContentPresenter.Content; }
    set { _ContentPresenter.Content = value; }
  }
}

2
请参见http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/81eca7d5-88d7-477a-8cdb-cfb9e8b75379/。 - Ryan Norbauer
非显而易见的。发现得很好。 - Basic
4
博客已经无人问津,但Wayback有一份快照。https://web.archive.org/web/20130126075829/http://www.switchonthecode.com/tutorials/wpf-tutorial-using-a-visual-collection - Kirk Kuykendall

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