Silverlight Popup positioning
I found myself frustrated recently with the positioning of Popups in Silverlight. I was surprised that web searches didn't seem to turn up accurate information, so I'll offer some things I've found about Popups hoping I'll help those that find themselves in similar circumstances (surely I'm not alone). The position of the popup seems to be based on the following parameters:
- The type of Parent (if parented)
- The position within the Parent (if parented)
- The VerticalOffset property
- The HorizontalOffset property
The Silverlight documentation gives the impression that you must always have the Popup parented in the visual tree, which I've found not to be the case. For instance, it seems to work to simply instantiate a Popup, set it's Child to some visual element, and set its IsOpen property to true. I'm releived this is the case, because requiring that the parent actually be in the visual tree of essentially a different layer is a little strange at best, and at worst can lead to circumlocution of the control hierarchy to accomodate such a hack. For instance, if a popup is to appear next to a control hosted by a content control, one would have to group the control into a panel in order to allow the popup, even though the popup doesn't have anything directly to do with the control's layout.
Anyway, if a Popup is not parented in a visual tree, the VerticalOffset and HorizontalOffset seem to work as published; namely, they represent the position of the top, left corner of the Popup relative to the global Silverlight coordinate system. This is, IMO, the preferred method for dealing with Popup positioning if your situation allows it. Otherwise, read on.
If the Popup is located in the "main" visual tree (ie isn't in the visual tree of another popup), the offsets are relative to that point within the visual tree. For instance, if a Popup is located in a StackPanel underneath a Button, the Popup will appear at the location it would appear at if the Popup's child were instead at that location. Well actually, that isn't completely precise, note that if the Popup's child were actually at the point the Popup appears, the surrounding layout might be affected by the size of the child. To the hosting layout, the Popup appears to occupy no space. However, the Popup's origin is not located the same as it would be if the Popup were treated as a 0 pixel object. If the popup is given more than isn't needed space (such as in the example case of the StackPanel with a button), the Popup seems to orient itself with the top, left of its given space. I have not experimented to see if this is only done in relation to the extent of the Popup's child size.
The situation is more complex, however if the Popup is hosted in the visual tree of another Popup, the offset is relative to the parent popup's coordinates. Not the parent popup's actual global coordinate, the parent popup's local coordinates. In order to ensure that a given popup appears where it actually should be relatively to the on-screen visuals, some parent walking is required. Here is what I've found to work:
var LPopup = FindParentPopup(this);
if (LPopup != null)
{
var LParentPopup = FindParentPopup(LPopup);
var LTransform = LPopup.TransformToVisual(LParentPopup == null ? null : LParentPopup);
var LOrigin = LTransform.Transform(new Point(0d, 0d));
Popup.VerticalOffset = LOrigin.Y;
Popup.HorizontalOffset = LOrigin.X;
}
else
{
Popup.VerticalOffset = 0d;
Popup.HorizontalOffset = 0d;
}
public static Popup FindParentPopup(DependencyObject AObject)
{
var LParent = VisualTreeHelper.GetParent(AObject);
if (LParent != null)
return FindParentPopup(LParent);
else if (AObject is FrameworkElement && ((FrameworkElement)AObject).Parent is Popup)
return (Popup)((FrameworkElement)AObject).Parent;
else
return null;
}