using ControlzEx; using System; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Animation; namespace MaterialDesignThemes.Wpf { /// /// Defines how the popup is aligned to the toggle part of the control. /// public enum PopupBoxPlacementMode { /// /// Display the popup below the toggle, and align the left edges.3 /// BottomAndAlignLeftEdges, /// /// Display the popup below the toggle, and align the right edges. /// BottomAndAlignRightEdges, /// /// Display the popup below the toggle, and align the center of the popup with the center of the toggle. /// BottomAndAlignCentres, /// /// Display the popup above the toggle, and align the left edges. /// TopAndAlignLeftEdges, /// /// Display the popup above the toggle, and align the right edges. /// TopAndAlignRightEdges, /// /// Display the popup above the toggle, and align the center of the popup with the center of the toggle. /// TopAndAlignCentres, /// /// Display the popup to the left of the toggle, and align the top edges. /// LeftAndAlignTopEdges, /// /// Display the popup to the left of the toggle, and align the bottom edges. /// LeftAndAlignBottomEdges, /// /// Display the popup to the left of the toggle, and align the middles. /// LeftAndAlignMiddles, /// /// Display the popup to the right of the toggle, and align the top edges. /// RightAndAlignTopEdges, /// /// Display the popup to the right of the toggle, and align the bottom edges. /// RightAndAlignBottomEdges, /// /// Display the popup to the right of the toggle, and align the middles. /// RightAndAlignMiddles, } /// /// Defines what causes the to open it's popup. /// public enum PopupBoxPopupMode { /// /// Open when the toggle button is clicked. /// Click, /// /// Open when the mouse goes over the toggle button. /// MouseOver, /// /// Open when the mouse goes over the toggle button, or the space in which the popup box would occupy should it be open. /// MouseOverEager } /// /// Popup box, similar to a , but allows more customizable content. /// [TemplatePart(Name = PopupPartName, Type = typeof(Popup))] [TemplatePart(Name = PopupContentControlPartName, Type = typeof(ContentControl))] [TemplatePart(Name = TogglePartName, Type = typeof(ToggleButton))] [TemplateVisualState(GroupName = "PopupStates", Name = PopupIsOpenStateName)] [TemplateVisualState(GroupName = "PopupStates", Name = PopupIsClosedStateName)] [ContentProperty("PopupContent")] public class PopupBox : ContentControl { public const string PopupPartName = "PART_Popup"; public const string TogglePartName = "PART_Toggle"; public const string PopupContentControlPartName = "PART_PopupContentControl"; public const string PopupIsOpenStateName = "IsOpen"; public const string PopupIsClosedStateName = "IsClosed"; private PopupEx _popup; private ContentControl _popupContentControl; private ToggleButton _toggleButton; private Point _popupPointFromLastRequest; private Point _lastRelativePositon; static PopupBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupBox), new FrameworkPropertyMetadata(typeof(PopupBox))); ToolTipService.IsEnabledProperty.OverrideMetadata(typeof(PopupBox), new FrameworkPropertyMetadata(null, CoerceToolTipIsEnabled)); EventManager.RegisterClassHandler(typeof(PopupBox), Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCapture)); EventManager.RegisterClassHandler(typeof(PopupBox), Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseButtonDown), true); } public PopupBox() { LayoutUpdated += OnLayoutUpdated; } public static readonly DependencyProperty ToggleContentProperty = DependencyProperty.Register( nameof(ToggleContent), typeof (object), typeof (PopupBox), new PropertyMetadata(default(object))); /// /// Content to display in the toggle button. /// public object ToggleContent { get { return (object) GetValue(ToggleContentProperty); } set { SetValue(ToggleContentProperty, value); } } public static readonly DependencyProperty ToggleContentTemplateProperty = DependencyProperty.Register( nameof(ToggleContentTemplate), typeof (DataTemplate), typeof (PopupBox), new PropertyMetadata(default(DataTemplate))); /// /// Template for . /// public DataTemplate ToggleContentTemplate { get { return (DataTemplate) GetValue(ToggleContentTemplateProperty); } set { SetValue(ToggleContentTemplateProperty, value); } } public static readonly DependencyProperty ToggleCheckedContentProperty = DependencyProperty.Register( nameof(ToggleCheckedContent), typeof (object), typeof (PopupBox), new PropertyMetadata(default(object))); /// /// Content to display in the toggle when it's checked (when the popup is open). Optional; if not provided the is used. /// public object ToggleCheckedContent { get { return (object) GetValue(ToggleCheckedContentProperty); } set { SetValue(ToggleCheckedContentProperty, value); } } public static readonly DependencyProperty ToggleCheckedContentTemplateProperty = DependencyProperty.Register( nameof(ToggleCheckedContentTemplate), typeof (DataTemplate), typeof (PopupBox), new PropertyMetadata(default(DataTemplate))); /// /// Template for . /// public DataTemplate ToggleCheckedContentTemplate { get { return (DataTemplate) GetValue(ToggleCheckedContentTemplateProperty); } set { SetValue(ToggleCheckedContentTemplateProperty, value); } } public static readonly DependencyProperty ToggleCheckedContentCommandProperty = DependencyProperty.Register( nameof(ToggleCheckedContentCommand), typeof (ICommand), typeof (PopupBox), new PropertyMetadata(default(ICommand))); /// /// Command to execute if toggle is checked (popup is open) and is set. /// public ICommand ToggleCheckedContentCommand { get { return (ICommand) GetValue(ToggleCheckedContentCommandProperty); } set { SetValue(ToggleCheckedContentCommandProperty, value); } } public static readonly DependencyProperty ToggleCheckedContentCommandParameterProperty = DependencyProperty.Register( nameof(ToggleCheckedContentCommandParameter), typeof (object), typeof (PopupBox), new PropertyMetadata(default(object))); /// /// Command parameter to use in conjunction with . /// public object ToggleCheckedContentCommandParameter { get { return (object) GetValue(ToggleCheckedContentCommandParameterProperty); } set { SetValue(ToggleCheckedContentCommandParameterProperty, value); } } public static readonly DependencyProperty PopupContentProperty = DependencyProperty.Register( nameof(PopupContent), typeof (object), typeof (PopupBox), new PropertyMetadata(default(object))); /// /// Content to display in the content. /// public object PopupContent { get { return (object) GetValue(PopupContentProperty); } set { SetValue(PopupContentProperty, value); } } public static readonly DependencyProperty PopupContentTemplateProperty = DependencyProperty.Register( nameof(PopupContentTemplate), typeof (DataTemplate), typeof (PopupBox), new PropertyMetadata(default(DataTemplate))); /// /// Popup content template. /// public DataTemplate PopupContentTemplate { get { return (DataTemplate) GetValue(PopupContentTemplateProperty); } set { SetValue(PopupContentTemplateProperty, value); } } public static readonly DependencyProperty IsPopupOpenProperty = DependencyProperty.Register( nameof(IsPopupOpen), typeof (bool), typeof (PopupBox), new FrameworkPropertyMetadata(default(bool), IsPopupOpenPropertyChangedCallback)); private static void IsPopupOpenPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var popupBox = (PopupBox) dependencyObject; var newValue = (bool)dependencyPropertyChangedEventArgs.NewValue; if (popupBox.PopupMode == PopupBoxPopupMode.Click) { if (newValue) Mouse.Capture(popupBox, CaptureMode.SubTree); else Mouse.Capture(null); } popupBox.AnimateChildrenIn(!newValue); popupBox._popup?.RefreshPosition(); VisualStateManager.GoToState(popupBox, newValue ? PopupIsOpenStateName : PopupIsClosedStateName, true); if (newValue) popupBox.OnOpened(); else popupBox.OnClosed(); } /// /// Gets or sets whether the popup is currently open. /// public bool IsPopupOpen { get { return (bool) GetValue(IsPopupOpenProperty); } set { SetValue(IsPopupOpenProperty, value); } } public static readonly DependencyProperty StaysOpenProperty = DependencyProperty.Register( nameof(StaysOpen), typeof (bool), typeof (PopupBox), new PropertyMetadata(default(bool))); /// /// Indicates of the popup should stay open if a click occurs inside the popup. /// public bool StaysOpen { get { return (bool) GetValue(StaysOpenProperty); } set { SetValue(StaysOpenProperty, value); } } public static readonly DependencyProperty PlacementModeProperty = DependencyProperty.Register( nameof(PlacementMode), typeof (PopupBoxPlacementMode), typeof (PopupBox), new PropertyMetadata(default(PopupBoxPlacementMode), PlacementModePropertyChangedCallback)); private static void PlacementModePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { ((PopupBox)dependencyObject)._popup?.RefreshPosition(); } /// /// Gets or sets how the popup is aligned in relation to the toggle. /// public PopupBoxPlacementMode PlacementMode { get { return (PopupBoxPlacementMode) GetValue(PlacementModeProperty); } set { SetValue(PlacementModeProperty, value); } } public static readonly DependencyProperty PopupModeProperty = DependencyProperty.Register( nameof(PopupMode), typeof (PopupBoxPopupMode), typeof (PopupBox), new PropertyMetadata(default(PopupBoxPopupMode))); /// /// Gets or sets what trigger causes the popup to open. /// public PopupBoxPopupMode PopupMode { get { return (PopupBoxPopupMode) GetValue(PopupModeProperty); } set { SetValue(PopupModeProperty, value); } } /// /// Get or sets how to unfurl controls when opening the popups. Only child elements of type are animated. /// public static readonly DependencyProperty UnfurlOrientationProperty = DependencyProperty.Register( nameof(UnfurlOrientation), typeof (Orientation), typeof (PopupBox), new PropertyMetadata(Orientation.Vertical)); /// /// Gets or sets how to unfurl controls when opening the popups. Only child elements of type are animated. /// public Orientation UnfurlOrientation { get { return (Orientation) GetValue(UnfurlOrientationProperty); } set { SetValue(UnfurlOrientationProperty, value); } } /// /// Framework use. Provides the method used to position the popup. /// public CustomPopupPlacementCallback PopupPlacementMethod => GetPopupPlacement; /// /// Event raised when the checked toggled content (if set) is clicked. /// public static readonly RoutedEvent ToggleCheckedContentClickEvent = EventManager.RegisterRoutedEvent("ToggleCheckedContentClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(PopupBox)); /// /// Event raised when the checked toggled content (if set) is clicked. /// [Category("Behavior")] public event RoutedEventHandler ToggleCheckedContentClick { add { AddHandler(ToggleCheckedContentClickEvent, value); } remove { RemoveHandler(ToggleCheckedContentClickEvent, value); } } /// /// Raises . /// protected virtual void OnToggleCheckedContentClick() { var newEvent = new RoutedEventArgs(ToggleCheckedContentClickEvent, this); RaiseEvent(newEvent); } public static readonly RoutedEvent OpenedEvent = EventManager.RegisterRoutedEvent( "Opened", RoutingStrategy.Bubble, typeof(EventHandler), typeof(PopupBox)); /// /// Raised when the popup is opened. /// public event RoutedEventHandler Opened { add { AddHandler(OpenedEvent, value); } remove { RemoveHandler(OpenedEvent, value); } } /// /// Raises . /// protected virtual void OnOpened() { var newEvent = new RoutedEventArgs(OpenedEvent, this); RaiseEvent(newEvent); } public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent( "Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(PopupBox)); /// /// Raised when the popup is opened. /// public event RoutedEventHandler Closed { add { AddHandler(ClosedEvent, value); } remove { RemoveHandler(ClosedEvent, value); } } /// /// Raises . /// protected virtual void OnClosed() { var newEvent = new RoutedEventArgs(ClosedEvent, this); RaiseEvent(newEvent); } public override void OnApplyTemplate() { if (_popup != null) _popup.Loaded -= PopupOnLoaded; if (_toggleButton != null) _toggleButton.PreviewMouseLeftButtonUp -= ToggleButtonOnPreviewMouseLeftButtonUp; base.OnApplyTemplate(); _popup = GetTemplateChild(PopupPartName) as PopupEx; _popupContentControl = GetTemplateChild(PopupContentControlPartName) as ContentControl; _toggleButton = GetTemplateChild(TogglePartName) as ToggleButton; if (_popup != null) _popup.Loaded += PopupOnLoaded; if (_toggleButton != null) _toggleButton.PreviewMouseLeftButtonUp += ToggleButtonOnPreviewMouseLeftButtonUp; VisualStateManager.GoToState(this, IsPopupOpen ? PopupIsOpenStateName : PopupIsClosedStateName, false); } protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) { base.OnIsKeyboardFocusWithinChanged(e); if (IsPopupOpen && !IsKeyboardFocusWithin) { Close(); } } protected override void OnMouseEnter(MouseEventArgs e) { if (IsEnabled && IsLoaded && (PopupMode == PopupBoxPopupMode.MouseOverEager || PopupMode == PopupBoxPopupMode.MouseOver)) { if (_popupContentControl != null) { //if the invisible popup that is watching the mouse, isn't where we expected it to be //then the main popup toggle has been moved off screen...so we shouldn't show the popup content var inputSource = PresentationSource.FromVisual(_popupContentControl); if (inputSource != null) { var popupScreenPoint = _popupContentControl.PointToScreen(new Point()); popupScreenPoint.Offset(-_popupContentControl.Margin.Left, -_popupContentControl.Margin.Top); var expectedPopupScreenPoint = PointToScreen(_popupPointFromLastRequest); if (Math.Abs(popupScreenPoint.X - expectedPopupScreenPoint.X) > ActualWidth/3 || Math.Abs(popupScreenPoint.Y - expectedPopupScreenPoint.Y) > ActualHeight/3) return; } } SetCurrentValue(IsPopupOpenProperty, true); } base.OnMouseEnter(e); } private void OnLayoutUpdated(object sender, EventArgs eventArgs) { if (_popupContentControl != null && _popup != null && (PopupMode == PopupBoxPopupMode.MouseOver || PopupMode == PopupBoxPopupMode.MouseOverEager)) { Point relativePosition = _popupContentControl.TranslatePoint(new Point(), this); if (relativePosition != _lastRelativePositon) { _popup.RefreshPosition(); _lastRelativePositon = _popupContentControl.TranslatePoint(new Point(), this); } } } protected override void OnMouseLeave(MouseEventArgs e) { if (PopupMode == PopupBoxPopupMode.MouseOverEager || PopupMode == PopupBoxPopupMode.MouseOver) Close(); base.OnMouseEnter(e); } protected void Close() { if (IsPopupOpen) SetCurrentValue(IsPopupOpenProperty, false); } private CustomPopupPlacement[] GetPopupPlacement(Size popupSize, Size targetSize, Point offset) { double x, y; if (FlowDirection == FlowDirection.RightToLeft) offset.X += targetSize.Width / 2; switch (PlacementMode) { case PopupBoxPlacementMode.BottomAndAlignLeftEdges: x = 0 - Math.Abs(offset.X*3); y = targetSize.Height - Math.Abs(offset.Y); break; case PopupBoxPlacementMode.BottomAndAlignRightEdges: x = 0 - popupSize.Width + targetSize.Width - offset.X; y = targetSize.Height - Math.Abs(offset.Y); break; case PopupBoxPlacementMode.BottomAndAlignCentres: x = targetSize.Width/2 - popupSize.Width/2 - Math.Abs(offset.X*2); y = targetSize.Height - Math.Abs(offset.Y); break; case PopupBoxPlacementMode.TopAndAlignLeftEdges: x = 0 - Math.Abs(offset.X * 3); y = 0 - popupSize.Height - Math.Abs(offset.Y*2); break; case PopupBoxPlacementMode.TopAndAlignRightEdges: x = 0 - popupSize.Width + targetSize.Width - offset.X; y = 0 - popupSize.Height - Math.Abs(offset.Y * 2); break; case PopupBoxPlacementMode.TopAndAlignCentres: x = targetSize.Width/2 - popupSize.Width/2 - Math.Abs(offset.X*2); y = 0 - popupSize.Height - Math.Abs(offset.Y * 2); break; case PopupBoxPlacementMode.LeftAndAlignTopEdges: x = 0 - popupSize.Width - Math.Abs(offset.X * 2); y = 0 - Math.Abs(offset.Y * 3); break; case PopupBoxPlacementMode.LeftAndAlignBottomEdges: x = 0 - popupSize.Width - Math.Abs(offset.X * 2); y = 0 - (popupSize.Height - targetSize.Height); break; case PopupBoxPlacementMode.LeftAndAlignMiddles: x = 0 - popupSize.Width - Math.Abs(offset.X * 2); y = targetSize.Height / 2 - popupSize.Height / 2 - Math.Abs(offset.Y * 2); break; case PopupBoxPlacementMode.RightAndAlignTopEdges: x = targetSize.Width; y = 0 - Math.Abs(offset.X * 3); break; case PopupBoxPlacementMode.RightAndAlignBottomEdges: x = targetSize.Width; y = 0 - (popupSize.Height - targetSize.Height); break; case PopupBoxPlacementMode.RightAndAlignMiddles: x = targetSize.Width; y = targetSize.Height / 2 - popupSize.Height / 2 - Math.Abs(offset.Y * 2); break; default: throw new ArgumentOutOfRangeException(); } _popupPointFromLastRequest = new Point(x, y); return new[] {new CustomPopupPlacement(_popupPointFromLastRequest, PopupPrimaryAxis.Horizontal)}; } private void AnimateChildrenIn(bool reverse) { if (_popupContentControl == null) return; if (VisualTreeHelper.GetChildrenCount(_popupContentControl) != 1) return; var contentPresenter = VisualTreeHelper.GetChild(_popupContentControl, 0) as ContentPresenter; var controls = contentPresenter.VisualDepthFirstTraversal().OfType(); double translateCoordinateFrom; if ((PlacementMode == PopupBoxPlacementMode.TopAndAlignCentres || PlacementMode == PopupBoxPlacementMode.TopAndAlignLeftEdges || PlacementMode == PopupBoxPlacementMode.TopAndAlignRightEdges || PlacementMode == PopupBoxPlacementMode.LeftAndAlignBottomEdges || PlacementMode == PopupBoxPlacementMode.RightAndAlignBottomEdges || (UnfurlOrientation == Orientation.Horizontal && ( PlacementMode == PopupBoxPlacementMode.LeftAndAlignBottomEdges || PlacementMode == PopupBoxPlacementMode.LeftAndAlignMiddles || PlacementMode == PopupBoxPlacementMode.LeftAndAlignTopEdges )) )) { controls = controls.Reverse(); translateCoordinateFrom = 80; } else translateCoordinateFrom = -80; var translateCoordinatePath = "(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform." + (UnfurlOrientation == Orientation.Horizontal ? "X)" : "Y)"); var sineEase = new SineEase(); var i = 0; foreach (var uiElement in controls) { var deferredStart = i++*20; var deferredEnd = deferredStart+200.0; var absoluteZeroKeyTime = KeyTime.FromPercent(0.0); var deferredStartKeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(deferredStart)); var deferredEndKeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(deferredEnd)); var elementTranslateCoordinateFrom = translateCoordinateFrom * i; var translateTransform = new TranslateTransform( UnfurlOrientation == Orientation.Vertical ? 0 : elementTranslateCoordinateFrom, UnfurlOrientation == Orientation.Vertical ? elementTranslateCoordinateFrom : 0); var transformGroup = new TransformGroup { Children = new TransformCollection(new Transform[] { new ScaleTransform(0, 0), translateTransform }) }; uiElement.SetCurrentValue(RenderTransformOriginProperty, new Point(.5, .5)); uiElement.RenderTransform = transformGroup; var opacityAnimation = new DoubleAnimationUsingKeyFrames(); opacityAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, absoluteZeroKeyTime, sineEase)); opacityAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, deferredStartKeyTime, sineEase)); opacityAnimation.KeyFrames.Add(new EasingDoubleKeyFrame((double)uiElement.GetAnimationBaseValue(OpacityProperty), deferredEndKeyTime, sineEase)); Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity")); Storyboard.SetTarget(opacityAnimation, uiElement); var scaleXAnimation = new DoubleAnimationUsingKeyFrames(); scaleXAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, absoluteZeroKeyTime, sineEase)); scaleXAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, deferredStartKeyTime, sineEase)); scaleXAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(1, deferredEndKeyTime, sineEase)); Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)")); Storyboard.SetTarget(scaleXAnimation, uiElement); var scaleYAnimation = new DoubleAnimationUsingKeyFrames(); scaleYAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, absoluteZeroKeyTime, sineEase)); scaleYAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, deferredStartKeyTime, sineEase)); scaleYAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(1, deferredEndKeyTime, sineEase)); Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)")); Storyboard.SetTarget(scaleYAnimation, uiElement); var translateCoordinateAnimation = new DoubleAnimationUsingKeyFrames(); translateCoordinateAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(elementTranslateCoordinateFrom, absoluteZeroKeyTime, sineEase)); translateCoordinateAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(elementTranslateCoordinateFrom, deferredStartKeyTime, sineEase)); translateCoordinateAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(0, deferredEndKeyTime, sineEase)); Storyboard.SetTargetProperty(translateCoordinateAnimation, new PropertyPath(translateCoordinatePath)); Storyboard.SetTarget(translateCoordinateAnimation, uiElement); var storyboard = new Storyboard(); storyboard.Children.Add(opacityAnimation); storyboard.Children.Add(scaleXAnimation); storyboard.Children.Add(scaleYAnimation); storyboard.Children.Add(translateCoordinateAnimation); if (reverse) { storyboard.AutoReverse = true; storyboard.Begin(); storyboard.Seek(TimeSpan.FromMilliseconds(deferredEnd)); storyboard.Resume(); } else storyboard.Begin(); } } #region Capture [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr GetCapture(); private static void OnLostMouseCapture(object sender, MouseEventArgs e) { var popupBox = (PopupBox) sender; if (Equals(Mouse.Captured, popupBox)) return; if (Equals(e.OriginalSource, popupBox)) { if (Mouse.Captured == null || popupBox._popup == null) { if (!(Mouse.Captured as DependencyObject).IsDescendantOf(popupBox._popup)) { popupBox.Close(); } } } else { if ((Mouse.Captured as DependencyObject).GetVisualAncestry().Contains(popupBox._popup.Child)) { // Take capture if one of our children gave up capture (by closing their drop down) if (!popupBox.IsPopupOpen || Mouse.Captured != null || GetCapture() != IntPtr.Zero) return; Mouse.Capture(popupBox, CaptureMode.SubTree); e.Handled = true; } else { if (popupBox.StaysOpen && popupBox.IsPopupOpen) { // allow scrolling if (GetCapture() != IntPtr.Zero) return; // Take capture back because click happend outside of control Mouse.Capture(popupBox, CaptureMode.SubTree); e.Handled = true; } else { popupBox.Close(); } } } } private static void OnMouseButtonDown(object sender, MouseButtonEventArgs e) { var popupBox = (PopupBox) sender; if (!popupBox.IsKeyboardFocusWithin) { popupBox.Focus(); } e.Handled = true; if (Mouse.Captured == popupBox && e.OriginalSource == popupBox) { popupBox.Close(); } } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { if (IsPopupOpen && !StaysOpen) { Close(); e.Handled = true; } else base.OnMouseLeftButtonUp(e); } #endregion private void PopupOnLoaded(object sender, RoutedEventArgs routedEventArgs) { if (PopupMode == PopupBoxPopupMode.MouseOverEager) _popup.IsOpen = true; } private void ToggleButtonOnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs) { if (PopupMode == PopupBoxPopupMode.Click || !IsPopupOpen) return; if (ToggleCheckedContent != null) { OnToggleCheckedContentClick(); if (ToggleCheckedContentCommand != null && ToggleCheckedContentCommand.CanExecute(ToggleCheckedContentCommandParameter) ) { ToggleCheckedContentCommand.Execute(ToggleCheckedContentCommandParameter); } } Close(); Mouse.Capture(null); mouseButtonEventArgs.Handled = true; } private static object CoerceToolTipIsEnabled(DependencyObject dependencyObject, object value) { var popupBox = (PopupBox) dependencyObject; return popupBox.IsPopupOpen ? false : value; } } }