-
-
Save devlights/7d5c3b28e45269e6334b to your computer and use it in GitHub Desktop.
Draggable, Non Topmost, Click to BringToFront Popup Sample. (ドラッグ可能で状態に応じてTopMostを切り替えるPopup(クリックすると前面に来るよう調整済み))
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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