Phillip Trelford's Array

POKE 36879,255

Silverlight 5 Native Popup Windows

A popup is a window without a standard border that pops up above other controls to display contextual information like a tooltip, context menu or validation error. Silverlight’s browser heritage means it’s built-in popups may appear clipped inside their parent window.

Silverlight Validation Warning Clipped

When running on the desktop, as an out-of-browser full-trust application, it would be nice to have popups that appear outside of their parent window, as they do in WPF and WinForms,

Borderless

Silverlight 5’s native window support makes it possible to create popups that can break free of their parent window in out-of-browser applications. The main requirement for creating a native popup window is to be able to create a borderless window. A Silverlight Window can be made borderless simply by setting it’s WindowStyle property to None:

new Window
{
    Title = "Popup",
    WindowStyle = WindowStyle.None,
    TopMost = true,
    Visibility = Visibility.Collapsed
};

Note: set the Window’s width & height after you’ve set the style, otherwise it will appear with additional area to accommodate it’s no longer visible chrome.

Position

The absolute position of the popup can be set via the popup window’s Top and Left properties. To place the popup relative to a point in a parent window simply offset by the parent window’s Top and Left properties.

Example: Showing a Context Menu popup window on a right mouse button click:

private void MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    var parent = Window.GetWindow((DependencyObject) sender);
    var position = e.GetPosition(null);
    var popup =
        new Window
        {
            WindowStyle = WindowStyle.None,
            TopMost = true,
            Top = parent.Top + position.Y,
            Left = parent.Left + position.X,
        };
}

Chrome

If the parent window has chrome then the absolute position should be offset by this too:

int chromeWidth = 0;
int chromeHeight = 0;
var style =
    window == Application.Current.MainWindow
    ? Deployment.Current.OutOfBrowserSettings.WindowSettings.WindowStyle
    : window.WindowStyle;
if (style == WindowStyle.SingleBorderWindow)
{
    chromeWidth = 10;
    chromeHeight = 32;
}

Note: getting the WindowStyle property of the main window throws a NotImplementedException exception. To workaround this the WindowStyle property of WindowSettings can be used instead if the parent window is the main window.

Target

WPF’s Popup control provides a PlacementTarget property which specifies:

the element relative to which the Popup is positioned when it opens

This can be useful for positioning tool tips or validation errors

Position

The TransformToVisual method can be used to get the position of an element in a window:

var window = Window.GetWindow(PlacementTarget);
var transform = PlacementTarget.TransformToVisual(window.Content);
var position = transform.Transform(new Point(0, 0));

The position of the popup can be offset using a HorizontalOffset and VerticalOffset.

Placement

The Placement property specifies the mode of the popup:

public enum PlacementMode
{
    Absolute,
    Relative,
    Top, 
    Left, 
    Bottom, 
    Right
}

Orientating the popup based on the placement mode:

private Point GetPlacementPosition()
{
    switch (Placement)
    {
        case PlacementMode.Absolute:
            return new Point(HorizontalOffset, VerticalOffset);
        case PlacementMode.Relative:
            return GetRelativePosition(0, 0);
        case PlacementMode.Top:
            return GetRelativePosition(0, -this.Height);
        case PlacementMode.Bottom:
            return GetRelativePosition(0, PlacementTarget.ActualHeight);
        case PlacementMode.Left:
            return GetRelativePosition(-this.Width, 0);
        case PlacementMode.Right:
            return GetRelativePosition(PlacementTarget.ActualWidth, 0);
        default:
            throw new InvalidOperationException();
    }
}

Note: the PlacementTarget’s ActualWidth and ActualHeight properties are useful here.

Monitors

Just as a Silverlight in-browser popup can be clipped inside a window, an out-of-browser window can be clipped inside the display monitor. Using P/Invoke it is easy to find monitor information from the MonitorFromPoint and GetMonitorInfo Win32 functions.

private void PlaceWindowWithinDisplay(Point point)
{
    var monitor = DisplayMonitors.GetMonitorInfoFromPoint(point);
    if (point.Y < monitor.WorkArea.Top)
        point.Y = monitor.WorkArea.Top;
    if (point.Y + this.Height > monitor.WorkArea.Bottom)
        point.Y = monitor.WorkArea.Bottom - this.Height;
    if (point.X < monitor.WorkArea.Left)
        point.X = monitor.WorkArea.Left;
    if (point.X + this.Width > monitor.WorkArea.Right)
        point.X = monitor.WorkArea.Right - this.Width;
    _window.Top = point.Y;
    _window.Left = point.X;
}

TaskBar

Popup windows should not typically be visible in the TaskBar. To remove a window from the task bar it’s extended style needs to be set to WS_EX_NOACTIVATE:

internal static void RemoveFromTaskBar(IntPtr hwnd)
{
    SetWindowLong(hwnd, GWL_EXSTYLE, (int)WS_EX_NOACTIVATE);
}

Again this can be achieved easily with P/Invoke, full details are available in this post:

Focus

WPF’s Popup provides a useful StaysOpen property which defaults to true and indicates:

whether the Popup control closes when the control is no longer in focus

Again this is easy to emulate by handling the GotFocus event on the popup’s parent window.

var window = Window.GetWindow(PlacementTarget);
window.Content.GotFocus += TargetWindowGotFocus;

Source

A full implementation of native Popup windows for Silverlight is available in the CodePlex Open Source project:

It also includes a Context Menu that works as a Popup:

ContextMenu_SL5_Native

Pingbacks and trackbacks (1)+

Comments are closed