Skip to main content

C# WPF Tutorial - Using A Visual Collection [Intermediate]


While WPF and XAML make the common 90% of UI programming quite easy, sometimes it gets a little odd in those other 10%. For instance - the visual tree. Most of the time it works great, and you never need to do anything special with it. But what about when you specifically want to give a user control children? Or maybe you want to give an adorner an actual visual child, instead of using OnRender? It isn't really obvious right away how to go about doing that.

But while it is not obvious, it is possible - and so today we are going to take a look at using VisualCollection to do those things. Specifically, we are going to create an Adorner to be used in an AdornerLayer that accepts a Visual as content. This is not possible right out of the gate, because Adorners don't have any built in way to have Visual children, and we can't use any of the normal controls that do (Panel, ContentPresenter, etc...), because only Adorners can be placed on an AdornerLayer.

Now, the example we are going to build today to use this adorner that can present content is pretty silly, but it is actually quite useful. I've used it a number of times for drag and drop (giving visual representations of what is being dragged and what will happen when it drops). Ok, now for some code:
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; }
  }
}
 
That is the entirety of the code for the AdornerContentPresenter. Not a whole lot of code, but a lot is going on, so I'll go though each function. First off, the VisualCollection. As you can see in the constructor, we create a VisualCollection right off the bat. The constructor for a VisualCollection takes a single argument - the owner. The owner is always the object that will be displaying the items in the collection, so in this case it is the Adorner itself. Next, we make a ContentPresenter and add it to the VisualCollection. You might think that is a little odd, but it actually makes our life a good bit easier.

You see, the content that is given to this will always be handed to this ContentPresenter. The ContentPresenter is good at exactly one thing - presenting pretty much any type of content. This way, we don't have to worry about how to display any type of content - we just have to worry about displaying a ContentPresenter, and the ContentPresenter worries about all the complex stuff. So this ContentPresenter will always be the only member of the VisualCollection for the Adorner.

Ok, now on to that second constructor - nothing special here, this is just a convenience to set the content right during creation.

Next, the measure and arrange. This is where using the ContentPresenter comes in handy. Pretty much, we just pass through the Measure and Arrange calls straight to the ContentPresenter, and return the values that comes back (the DesiredSize property for Measure, and the RenderSize property for arrange).

Following those two methods are two items that you have probably never had a reason to mess with before: GetVisualChild and VisualChildrenCount. These are extremely important to override whenever you start playing with the children in a control. Since we have an internal VisualCollection, we can just pass the requests on to that collection. For GetVisualChild, we return the item at the requested index from the VisualCollection, and for VisualChildrenCount we return the number of items in the VisualCollection.

Finally, the Content property. All that this property does is set or get the content of our internal ContentPresenter. And thats it!

Now, you are probably wondering why I have the VisualCollection in this class at all - I sure did at first. On the surface it seems that it serves no purpose at all - and in fact stuff would "mostly" work if you weren't using it. Actually, for a while, in that Drag+Drop Adorner I was talking about earlier, I didn't use a VisualCollection, and nothing seemed wrong - until certain stuff just didn't work (like hit testing and global styles). It turns out that the VisualCollection does do some very important work underneath the surface - it maintains the parent-child connections between the owner of the collection and any items added to it. And, after some digging, it turns out that using a VisualCollection is one of two possible ways to maintain these connections. The other way is to use the protected methods AddVisualChild and RemoveVisualChild, although Microsoft generally recommends using a VisualCollection over using those two methods.

Ok, now for the silly example for how to use this AdornerContentPresenter:
<Window x:Class="VisualCollectionExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="100" Width="300">
  <AdornerDecorator>
    <Rectangle Fill="Black" Width="50" Height="50" x:Name="Block" />
  </AdornerDecorator>
</Window>

public partial class Window1 : Window
{
  public Window1()
  {
    InitializeComponent();

    AdornerLayer layer = 
        AdornerLayer.GetAdornerLayer(Block);
    AdornerContentPresenter adc = 
        new AdornerContentPresenter(Block);

    adc.Content = new TextBlock() { 
        Text = "Hi There", 
        Foreground = Brushes.White
    };
    layer.Add(adc);
  }
}
 
Exactly what you probably expected. That code will show a black rectangle, and on top of the rectangle will the the text "Hi There" in white in the adorner layer.

Well, that is it for using a VisualCollection. Just is case you were wondering, there is an equivalent collection called UIElementCollection that is used when you want to automatically maintain the connections for both the WPF visual and logical trees - but perhaps that is content for a future tutorial. For now, you can grab the Visual Studio solution for the code above here.

Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...