This was a hard one…
One of the things that is very much missing in Silverlight is closing a Popup upon clicking outside of it.
this is implemented as StaysOpen (true/false) in WPF.
but is very much missing in Silverlight Popup.
i wanted to make it as simple as possible, without any dependencies on other dll, and support binding changes.
so an attached property was the way to go.
lots of thanks to Vladi (koganvladimir at yahoo dot com) on helping here.
to use it simply add the attached property to your Popup, when the value is set to False the popup will be closed upon clicking outside.
here are two common use case examples:
<Popup helpers:PopupHelper.StaysOpen="False">
<Popup helpers:PopupHelper.StaysOpen="{Binding SomeChangingBool}">
and here is the PopupHelper.cs class:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Helpers
{
public class PopupHelper
{
public static bool GetStaysOpen(DependencyObject obj)
{
return (bool)obj.GetValue(StaysOpenProperty);
}
public static void SetStaysOpen(DependencyObject obj, bool value)
{
obj.SetValue(StaysOpenProperty, value);
}
public static readonly DependencyProperty StaysOpenProperty =
DependencyProperty.RegisterAttached("StaysOpen", typeof(bool), typeof(PopupHelper),
new PropertyMetadata(true, StaysOpenChanged));
private static void StaysOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pop = d as Popup;
if (pop == null) return;
// this is the only way i could find to tell (or wait) for after loaded.
if (pop.Child == null)
{
pop.Loaded += PopOnLoaded;
}
else
{
UpdateStaysOpen(pop, (bool)e.NewValue);
}
}
private static void PopOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var pop = sender as Popup;
if (pop == null) return;
pop.Loaded -= PopOnLoaded;
UpdateStaysOpen(pop, (bool)pop.GetValue(StaysOpenProperty));
}
private static void UpdateStaysOpen(Popup popup, bool stayOpen)
{
var blocker = GetBlocker(popup);
if (blocker == null)
{
SetBlockerLayer(popup);
blocker = GetBlocker(popup);
}
blocker.IsHitTestVisible = !stayOpen;
}
private static Canvas GetBlocker(Popup pop)
{
var elementPopupChildCanvas = pop.Child as FrameworkElement;
var blocker = VisualTreeHelper.GetChild(elementPopupChildCanvas, 0) as FrameworkElement;
Canvas retVal;
if ((retVal = blocker as Canvas) != null && blocker.Tag.ToString() == "ElementPopupBlocker")
{
return retVal;
}
return null;
}
private static void SetBlockerLayer(Popup popup)
{
var popupChild = popup.Child as FrameworkElement;
if (popupChild == null) return;
var blocker = new Canvas { Background = new SolidColorBrush(Colors.Gray), Tag = "ElementPopupBlocker" };
blocker.MouseLeftButtonDown += delegate { popup.IsOpen = false; };
var elementPopupChildCanvas = new Canvas();
popup.Child = elementPopupChildCanvas;
elementPopupChildCanvas.Children.Add(blocker);
elementPopupChildCanvas.Children.Add(popupChild);
popupChild.HorizontalAlignment = HorizontalAlignment.Left;
popupChild.VerticalAlignment = VerticalAlignment.Top;
Canvas.SetLeft(popupChild, popup.HorizontalOffset);
Canvas.SetTop(popupChild, popup.VerticalOffset);
popup.HorizontalOffset = 0.0;
popup.VerticalOffset = 0.0;
popup.LayoutUpdated += delegate { Arrange(popup); };
}
private static void Arrange(Popup popup)
{
if (!popup.IsOpen) return;
var elementPopupChildCanvas = popup.Child;
if (elementPopupChildCanvas == null) return;
var blocker = VisualTreeHelper.GetChild(elementPopupChildCanvas, 0) as Canvas;
var popupChild = VisualTreeHelper.GetChild(elementPopupChildCanvas, 1) as FrameworkElement;
if (blocker == null || popupChild == null) return;
var width = Application.Current.Host.Content.ActualWidth;
var height = Application.Current.Host.Content.ActualHeight;
if (height < 50.0 || width < 50.0)
return;
var generalTransform = popup.TransformToVisual(null);
var point1 = new Point(0.0, 0.0);
var point2 = new Point(1.0, 0.0);
var point3 = new Point(0.0, 1.0);
var point4 = generalTransform.Transform(point1);
var point5 = generalTransform.Transform(point2);
var point6 = generalTransform.Transform(point3);
var identity = Matrix.Identity;
identity.M11 = point5.X - point4.X;
identity.M12 = point5.Y - point4.Y;
identity.M21 = point6.X - point4.X;
identity.M22 = point6.Y - point4.Y;
identity.OffsetX = point4.X;
identity.OffsetY = point4.Y;
var num = identity.M11 * identity.M22 - identity.M12 * identity.M21;
var matrix = identity;
identity.M11 = matrix.M22 / num;
identity.M12 = -1.0 * matrix.M12 / num;
identity.M21 = -1.0 * matrix.M21 / num;
identity.M22 = matrix.M11 / num;
identity.OffsetX = (matrix.OffsetY * matrix.M21 - matrix.OffsetX * matrix.M22) / num;
identity.OffsetY = (matrix.OffsetX * matrix.M12 - matrix.OffsetY * matrix.M11) / num;
blocker.Width = width;
blocker.Height = height;
blocker.RenderTransform = new MatrixTransform { Matrix = identity };
}
}
}