Skip to content

Instantly share code, notes, and snippets.

@devlights
Forked from flq/NonTopmostPopup.cs
Last active August 29, 2015 14:22
Show Gist options
  • Save devlights/7d5c3b28e45269e6334b to your computer and use it in GitHub Desktop.
Save devlights/7d5c3b28e45269e6334b to your computer and use it in GitHub Desktop.
Draggable, Non Topmost, Click to BringToFront Popup Sample. (ドラッグ可能で状態に応じてTopMostを切り替えるPopup(クリックすると前面に来るよう調整済み))
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:exControls="clr-namespace:NonTopmostPopupSample.ExControls"
xmlns:behaviors="clr-namespace:NonTopmostPopupSample.Behaviors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="NonTopmostPopupSample.MainWindow"
Title="Non Topmost Popup Sample"
Height="426"
Width="525"
WindowState="Maximized">
<Window.Resources>
<Storyboard x:Key="ShowPopup">
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(Popup.IsOpen)" Storyboard.TargetName="popup1">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(Popup.IsOpen)" Storyboard.TargetName="popup2">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(Popup.IsOpen)" Storyboard.TargetName="popup3">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button">
<BeginStoryboard x:Name="ShowPopup_BeginStoryboard" Storyboard="{StaticResource ShowPopup}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Main Screen" VerticalAlignment="Top" FontSize="64" Grid.RowSpan="2"/>
<Button x:Name="button" Content="Show Popup" HorizontalAlignment="Left" Margin="10,240,0,0" VerticalAlignment="Top" Width="310" FontSize="26.667"/>
<exControls:NonTopmostPopup x:Name="popup1" PopupAnimation="Scroll" AllowsTransparency="True" Placement="Center">
<i:Interaction.Behaviors>
<behaviors:DragPopupBehavior/>
<behaviors:ClickToBringToFrontPopupBehavior/>
</i:Interaction.Behaviors>
<Grid Background="#FFE4A5A5" Height="250" Width="350">
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="popup 1" VerticalAlignment="Top" FontSize="29.333"/>
</Grid>
</exControls:NonTopmostPopup>
<exControls:NonTopmostPopup x:Name="popup2" PopupAnimation="Fade" AllowsTransparency="True" Placement="Center" HorizontalOffset="500" VerticalOffset="50">
<i:Interaction.Behaviors>
<behaviors:DragPopupBehavior/>
<behaviors:ClickToBringToFrontPopupBehavior/>
</i:Interaction.Behaviors>
<Grid Background="#FFA5CAE4" Height="250" Width="350">
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="popup 2" VerticalAlignment="Top" FontSize="29.333"/>
</Grid>
</exControls:NonTopmostPopup>
<exControls:NonTopmostPopup x:Name="popup3" PopupAnimation="Slide" AllowsTransparency="True" Placement="Center" HorizontalOffset="-100" VerticalOffset="200" IsTopmost="False">
<i:Interaction.Behaviors>
<behaviors:DragPopupBehavior/>
<behaviors:ClickToBringToFrontPopupBehavior/>
</i:Interaction.Behaviors>
<Grid Background="#FFE0DBA5" Height="250" Width="350">
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="popup 3" VerticalAlignment="Top" FontSize="29.333"/>
</Grid>
</exControls:NonTopmostPopup>
</Grid>
</Window>
namespace NonTopmostPopupSample
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Interop;
namespace NativeApis
{
public class WinApi
{
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
public static readonly IntPtr HWND_TOP = new IntPtr(0);
public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
public const UInt32 SWP_NOSIZE = 0x0001;
public const UInt32 SWP_NOMOVE = 0x0002;
public const UInt32 SWP_NOZORDER = 0x0004;
public const UInt32 SWP_NOREDRAW = 0x0008;
public const UInt32 SWP_NOACTIVATE = 0x0010;
public const UInt32 SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
public const UInt32 SWP_SHOWWINDOW = 0x0040;
public const UInt32 SWP_HIDEWINDOW = 0x0080;
public const UInt32 SWP_NOCOPYBITS = 0x0100;
public const UInt32 SWP_NOOWNERZORDER = 0x0200; /* Don’t do owner Z ordering */
public const UInt32 SWP_NOSENDCHANGING = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */
public const UInt32 TOPMOST_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
}
}
namespace ExControls
{
using NativeApis;
/// <summary>
/// Popup with code to not be the topmost control
/// </summary>
/// <remarks>
/// https://chriscavanagh.wordpress.com/2008/08/13/non-topmost-wpf-popup/
/// https://gist.github.com/flq/903202#file-nontopmostpopup-cs
/// </remarks>
public class NonTopmostPopup : Popup
{
/// <summary>
/// Is Topmost dependency property
/// </summary>
public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(NonTopmostPopup), new FrameworkPropertyMetadata(false, OnIsTopmostChanged));
private bool? _appliedTopMost;
private bool _alreadyLoaded;
private Window _parentWindow;
/// <summary>
/// Get/Set IsTopmost
/// </summary>
public bool IsTopmost
{
get { return (bool)GetValue(IsTopmostProperty); }
set { SetValue(IsTopmostProperty, value); }
}
/// <summary>
/// ctor
/// </summary>
public NonTopmostPopup()
{
Loaded += OnPopupLoaded;
Unloaded += OnPopupUnloaded;
}
void OnPopupLoaded(object sender, RoutedEventArgs e)
{
if (_alreadyLoaded)
return;
_alreadyLoaded = true;
if (Child != null)
{
Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
}
_parentWindow = Window.GetWindow(this);
if (_parentWindow == null)
return;
_parentWindow.Activated += OnParentWindowActivated;
_parentWindow.Deactivated += OnParentWindowDeactivated;
}
private void OnPopupUnloaded(object sender, RoutedEventArgs e)
{
if (_parentWindow == null)
return;
_parentWindow.Activated -= OnParentWindowActivated;
_parentWindow.Deactivated -= OnParentWindowDeactivated;
}
void OnParentWindowActivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Activated");
SetTopmostState(true);
}
void OnParentWindowDeactivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Deactivated");
if (IsTopmost == false)
{
SetTopmostState(IsTopmost);
}
}
void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Child Mouse Left Button Down");
SetTopmostState(true);
if (!_parentWindow.IsActive && IsTopmost == false)
{
_parentWindow.Activate();
Debug.WriteLine("Activating Parent from child Left Button Down");
}
}
private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var thisobj = (NonTopmostPopup)obj;
thisobj.SetTopmostState(thisobj.IsTopmost);
}
protected override void OnOpened(EventArgs e)
{
SetTopmostState(IsTopmost);
base.OnOpened(e);
}
private void SetTopmostState(bool isTop)
{
// Don’t apply state if it’s the same as incoming state
if (_appliedTopMost.HasValue && _appliedTopMost == isTop)
{
return;
}
if (Child == null)
return;
var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;
if (hwndSource == null)
return;
var hwnd = hwndSource.Handle;
WinApi.RECT rect;
if (!WinApi.GetWindowRect(hwnd, out rect))
return;
Debug.WriteLine("setting z-order " + isTop);
if (isTop)
{
WinApi.SetWindowPos(hwnd, WinApi.HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, WinApi.TOPMOST_FLAGS);
}
else
{
// Z-Order would only get refreshed/reflected if clicking the
// the titlebar (as opposed to other parts of the external
// window) unless I first set the popup to HWND_BOTTOM
// then HWND_TOP before HWND_NOTOPMOST
WinApi.SetWindowPos(hwnd, WinApi.HWND_BOTTOM, rect.Left, rect.Top, (int)Width, (int)Height, WinApi.TOPMOST_FLAGS);
WinApi.SetWindowPos(hwnd, WinApi.HWND_TOP, rect.Left, rect.Top, (int)Width, (int)Height, WinApi.TOPMOST_FLAGS);
WinApi.SetWindowPos(hwnd, WinApi.HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, WinApi.TOPMOST_FLAGS);
}
_appliedTopMost = isTop;
}
}
}
namespace Behaviors
{
using NativeApis;
/// <summary>
/// http://stackoverflow.com/questions/4784240/a-draggable-popup-control-in-wpf
/// </summary>
public class DragPopupBehavior : Behavior<Popup>
{
bool _mouseDown;
Point _oldPosition;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
}
protected override void OnDetaching()
{
AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
base.OnDetaching();
}
void AssociatedObject_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_mouseDown = false;
AssociatedObject.Child.ReleaseMouseCapture();
}
void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!_mouseDown)
{
return;
}
var newPosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
var offset = (newPosition - _oldPosition);
_oldPosition = newPosition;
AssociatedObject.HorizontalOffset += offset.X;
AssociatedObject.VerticalOffset += offset.Y;
}
void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_mouseDown = true;
_oldPosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
AssociatedObject.Child.CaptureMouse();
}
}
public class ClickToBringToFrontPopupBehavior : Behavior<Popup>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += ExecMouseLeftButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseLeftButtonDown -= ExecMouseLeftButtonDown;
}
protected virtual void ExecMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var window = Window.GetWindow(AssociatedObject);
var hwndSource = (PresentationSource.FromVisual(AssociatedObject.Child)) as HwndSource;
if (hwndSource == null)
{
return;
}
var hwnd = hwndSource.Handle;
WinApi.RECT rect;
if (!WinApi.GetWindowRect(hwnd, out rect))
{
return;
}
WinApi.SetWindowPos(hwnd, WinApi.HWND_TOPMOST, rect.Left, rect.Top, (int)window.Width, (int)window.Height, WinApi.TOPMOST_FLAGS);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment