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

C# 5 CallerMemberName from Silverlight & WPF 4.0

C# 5 allows you to obtain the method or property of the caller to a method using the CallerMemberName attribute under System.Runtime.CompilerServices in .Net 4.5:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ObservableObject : INotifyPropertyChanged 
{
  protected void NotifyPropertyChanged([CallerMemberName] string name = null)
  {
    var e = PropertyChanged;
    if (e != null) e(this, new PropertyChangedEventArgs(name));
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

This is particularly useful in XAML applications using WPF, Silverlight or WinRT that signal changes to properties via the INotifyPropertyChanged interface. With the new feature you don’t need to explicitly specify a literal string or lambda expression when notifying that a property has changed from it’s setter:

public class ViewModel : ObservableObject
{
  private object _value;

  public object Value
  {
    get { return _value; }
    set
    {
      _value = value;
      this.NotifyPropertyChanged();
    }
  }
}

Silverlight and earlier versions of the .Net Framework behind WPF do not have the CallerMemberName attribute. The good news is that you simply need to define it in yourself and assuming you’re using the C# 5 compiler then it just works:

namespace System.Runtime.CompilerServices
{
  /// <summary>
  /// Allows you to obtain the method or property name of the caller.
  /// </summary>
  [AttributeUsageAttribute(AttributeTargets.Parameter, Inherited = false)]
  public sealed class CallerMemberNameAttribute : Attribute { }
}

Silverlight 5 Native Modal Windows

Multiple Window support is new in Silverlight 5:

You can display multiple Window instances in trusted, out-of-browser applications. This enables you to create non-modal dialog boxes, tear-off windows, and user-adjustable layouts that can span multiple monitors.

Silverlight provides support for modal dialog boxes that work in-browser and out-of-browser against the main window, like the ChildWindow control. However Silverlight’s built-in support for native Modal Windows is limited to the MessageBox class

MessageBox

Silverlight’s MessageBox.Show static method can open a modal dialog box, which is good for error messages, but it looks a bit basic and you can’t modify it’s style:

MessageBox.Show

ShowDialog

WPF provides a Window.ShowDialog method:

ShowDialog shows the window, disables all other windows in the application, and returns only when the window is closed.

This mechanism allows you to provide your own styling on the window.

Using an extension method, Silverlight’s Window class can be extended with a ShowDialog method too. This can be implemented very concisely in F#:

type System.Windows.Window with
   member window.ShowDialog() = 
      async {
          // Show the window
          window.Show()
          // Disable all other windows
          allWindows() |> Seq.filter ((<>) window) |> Seq.iter disableWindow
          // Await window closing
          do! window.Closing |> Async.AwaitEvent |> Async.Ignore
          // Enable all other windows
          allWindows() |> Seq.filter ((<>) window) |> Seq.iter enableWindow
      } |> Async.StartImmediate

Win32 provides an EnableWindow function that we can P/Invoke:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

The EnableWindow function takes a handle to a Window. The code to find a window’s handle is available in the article Silverlight 5 Native Windows P/Invoke that I wrote earlier in the month. The article also demonstrates how a window can be removed from the taskbar which you may want to do for a dialog window.

ShowModal

The ShowDialog method disables all other windows while the Window is shown. Let’s look at a ShowModal extension method in C# that only disable’s the owner window:

public static class ModalWindowExtension
{
    public static void ShowModal(this Window modalWindow, Window ownerWindow)
    {
        ModalWindow.Show(modalWindow, ownerWindow);
    }
}

An internal class instance is required so that we can hook and unhook the Closing event:

internal class ModalWindow
{
    Action _close = () => { };

    internal static void Show(Window modalWindow, Window ownerWindow)
    {
        var instance = new ModalWindow();
        instance.Invoke(modalWindow, ownerWindow);
    }

    internal void Invoke(Window modalWindow, Window ownerWindow)
    {
        modalWindow.Closing += WindowClosing;

        var ownerHwnd = Win32.FindHwnd(ownerWindow);
        bool isOwnerEnabled = Win32.IsWindowEnabled(ownerHwnd);
        Win32.EnableWindow(ownerHwnd, false);

        _close = () =>
        {
            modalWindow.Closing -= WindowClosing;
            Win32.EnableWindow(ownerHwnd, isOwnerEnabled);
            _close = () => { };
        };

        modalWindow.Show();
    }

    private void WindowClosing(object sender, ClosingEventArgs e)
    {
        _close();
    }
}

The C# implementation has to deal with some extra accidental complexity as C# events are not first class, beside that the implementations are quite similar.

Summary

Adding native modal window support for Silverlight 5 out-of-browser applications is pretty easy using the WIn32 EnableWindow and an extension method.