C# 带有控件的 WPF 装饰器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9998691/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 12:04:11  来源:igfitidea点击:

WPF adorner with controls inside

c#.netwpf

提问by Ryan Norbauer

I am trying to achieve an unusual use of an Adorner. When you mouse-over a RichTextBox, an Adorner (see diagram below) will appear above it, allowing you to add a list of strings to a ListBox contained in the Adorner. This is used for adding "tags" (à la Flickr) to the passage contained in the adorned element.

我正在尝试实现对 Adorner 的不寻常使用。当您将鼠标悬停在 RichTextBox 上时,其上方将出现一个 Adorner(见下图),允许您将字符串列表添加到包含在 Adorner 中的 ListBox。这用于向装饰元素中包含的段落添加“标签”(à la Flickr)。

adorner diagram

装饰图

Firstly: is this even possible?

首先:这甚至可能吗?

Most examples of Adorners show how to override the Adorner's OnRender method to do trivial things like draw shapes. I was able to use this to render a set of rectangles that creates the gray border of the Adorner, which also resizes automatically if the height of RichTextBox increases due to additional lines text being added while the Adorner is displayed.

Adorner 的大多数示例都展示了如何覆盖 Adorner 的 OnRender 方法来执行诸如绘制形状之类的琐碎事情。我能够使用它来渲染一组矩形,这些矩形创建了装饰器的灰色边框,如果在显示装饰器时添加了额外的行文本,RichTextBox 的高度增加,它也会自动调整大小。

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

}

However, adding controls is slightly more problematic. Generally speaking, WPF's adorner requires adding child controls in code rather than XAML. Using the technique described in DrawingContext adorner - possible to draw stackpanel?, I have learned how to add child controls (like a TextBox) to an Adorner without any problem within the Adorner's initializer.

但是,添加控件稍微有点问题。一般来说,WPF 的装饰器需要在代码中添加子控件而不是 XAML。使用DrawingContext adorner 中描述的技术- 可以绘制堆栈面板吗?,我已经学会了如何将子控件(如 TextBox)添加到 Adorner,而不会在 Adorner 的初始值设定项中出现任何问题。

The issue, however, is the placement of those controls within the Adorner.

然而,问题是这些控件在 Adorner 中的放置。

If I could create a grid with a gray background and position it at the bottom of the Adorner, I should be good to go. I would assume (hope) that things like automatic resizing of the Adorner based on the changing size of that Grid as tags are added would then happen automatically.

如果我可以创建一个带有灰色背景的网格并将其放置在 Adorner 的底部,我应该很高兴。我会假设(希望)像添加标签时根据网格大小的变化自动调整装饰器大小的事情会自动发生。

In short, assuming this is possible, can anyone recommend a way of creating this lower tagging control area withinthe Adorner and positioning it relative to the bottom of Adorner (which may possibly have to resize as the RichTextBox content resizes)?

简而言之,假设这是可能的,任何人都可以推荐一种在 Adorner创建此较低标记控制区域并将其相对于 Adorner 底部定位的方法(可能必须随着 RichTextBox 内容的大小调整而调整大小)?

采纳答案by Ryan Norbauer

Huzah! With the help of Ghenadie Tanasiev, I've got an answer.

胡扎!在Ghenadie Tanasiev的帮助下,我得到了答案。

Unlike most controls in WPF, adorners don't have any out-of-the-box way of assigning child elements (such as the controls I wanted to add). Without adding anything to adorners, you can only override their OnRendermethod and draw stuff within the DrawingContextthat gets passed into it. To be honest, this fits probably 99% of use cases for adorners (stuff like creating drag handles around an object), but I needed to add some propercontrols to my Adorner.

与 WPF 中的大多数控件不同,装饰器没有任何现成的分配子元素(例如我想添加的控件)的方式。无需向装饰器添加任何内容,您只能覆盖它们的OnRender方法并在DrawingContext传递给它的 中绘制内容。老实说,这可能适合 99% 的装饰器用例(比如在对象周围创建拖动手柄),但我需要为我的装饰器添加一些适当的控件。

The trick to doing this is to create a VisualCollectionand set your adorner as its owner by passing it into the constructor for the collection.

这样做的技巧是创建一个VisualCollection并将装饰器设置为其所有者,方法是将其传递给集合的构造函数。

This is all described pretty comprehensively in this blog article. Unfortunately, my repeated Google searches weren't turning this article up until I knew to search for VisualCollection, thanks to Ghenadie's guidance.

这一切都在这篇博客文章中进行了相当全面的描述。不幸的是,VisualCollection多亏了 Ghenadie 的指导,在我知道要搜索 之前,我反复的 Google 搜索并没有打开这篇文章。

This isn't mentioned in the article, but note that it is possible to combine the VisualCollection technique along with drawing in the OnRender method of the adorner. I'm using OnRender to achieve the side and top borders described in my diagram above and using VisualCollection to place and create the controls.

文章中没有提到这一点,但请注意,可以将 VisualCollection 技术与装饰器的 OnRender 方法中的绘图结合起来。我正在使用 OnRender 来实现上图中描述的侧边和顶部边框,并使用 VisualCollection 来放置和创建控件。

Edit:here is the source code from the mentioned blog post since it is no longer available:

编辑:这是上述博客文章中的源代码,因为它不再可用:

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