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
Post a Comment