为解决popup中输入法无法正常定位的问题,创建自定义popup控件,完整代码如下:
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
namespace MaterialDesignThemes.Wpf
{
/// <summary>
/// This custom popup can be used by validation error templates or something else.
/// It provides some additional nice features:
/// - repositioning if host-window size or location changed
/// - repositioning if host-window gets maximized and vice versa
/// - it's only topmost if the host-window is activated
/// </summary>
public class PopupEx : Popup
{
public static readonly DependencyProperty CloseOnMouseLeftButtonDownProperty
= DependencyProperty.Register(nameof(CloseOnMouseLeftButtonDown),
typeof(bool),
typeof(PopupEx),
new PropertyMetadata(false));
/// <summary>
/// Gets/sets if the popup can be closed by left mouse button down.
/// </summary>
public bool CloseOnMouseLeftButtonDown
{
get { return (bool)GetValue(CloseOnMouseLeftButtonDownProperty); }
set { SetValue(CloseOnMouseLeftButtonDownProperty, value); }
}
public static readonly DependencyProperty AllowTopMostProperty
= DependencyProperty.Register(nameof(AllowTopMost),
typeof(bool),
typeof(PopupEx),
new PropertyMetadata(true));
public bool AllowTopMost
{
get { return (bool)GetValue(AllowTopMostProperty); }
set { SetValue(AllowTopMostProperty, value); }
}
public PopupEx()
{
Loaded += PopupEx_Loaded;
Opened += PopupEx_Opened;
EventManager.RegisterClassHandler(typeof(PopupEx), Popup.PreviewGotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnPreviewGotKeyboardFocus), true);
}
private static void OnPreviewGotKeyboardFocus(Object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.NewFocus as TextBoxBase;
if (textBox != null)
{
var hwndSource = PresentationSource.FromVisual(textBox) as HwndSource;
if (hwndSource != null)
{
NativeMethods.SetActiveWindow(hwndSource.Handle);
}
}
}
/// <summary>
/// Causes the popup to update it's position according to it's current settings.
/// </summary>
public void RefreshPosition()
{
var offset = HorizontalOffset;
// "bump" the offset to cause the popup to reposition itself on its own
SetCurrentValue(HorizontalOffsetProperty, offset + 1);
SetCurrentValue(HorizontalOffsetProperty, offset);
}
private void PopupEx_Loaded(object sender, RoutedEventArgs e)
{
var target = PlacementTarget as FrameworkElement;
if (target == null)
{
return;
}
hostWindow = Window.GetWindow(target);
if (hostWindow == null)
{
return;
}
hostWindow.LocationChanged -= hostWindow_SizeOrLocationChanged;
hostWindow.LocationChanged += hostWindow_SizeOrLocationChanged;
hostWindow.SizeChanged -= hostWindow_SizeOrLocationChanged;
hostWindow.SizeChanged += hostWindow_SizeOrLocationChanged;
target.SizeChanged -= hostWindow_SizeOrLocationChanged;
target.SizeChanged += hostWindow_SizeOrLocationChanged;
hostWindow.StateChanged -= hostWindow_StateChanged;
hostWindow.StateChanged += hostWindow_StateChanged;
hostWindow.Activated -= hostWindow_Activated;
hostWindow.Activated += hostWindow_Activated;
hostWindow.Deactivated -= hostWindow_Deactivated;
hostWindow.Deactivated += hostWindow_Deactivated;
Unloaded -= PopupEx_Unloaded;
Unloaded += PopupEx_Unloaded;
}
private void PopupEx_Opened(object sender, EventArgs e)
{
SetTopmostState(hostWindow?.IsActive ?? true);
}
private void hostWindow_Activated(object sender, EventArgs e)
{
SetTopmostState(true);
}
private void hostWindow_Deactivated(object sender, EventArgs e)
{
SetTopmostState(false);
}
private void PopupEx_Unloaded(object sender, RoutedEventArgs e)
{
var target = PlacementTarget as FrameworkElement;
if (target != null)
{
target.SizeChanged -= hostWindow_SizeOrLocationChanged;
}
if (hostWindow != null)
{
hostWindow.LocationChanged -= hostWindow_SizeOrLocationChanged;
hostWindow.SizeChanged -= hostWindow_SizeOrLocationChanged;
hostWindow.StateChanged -= hostWindow_StateChanged;
hostWindow.Activated -= hostWindow_Activated;
hostWindow.Deactivated -= hostWindow_Deactivated;
}
Unloaded -= PopupEx_Unloaded;
Opened -= PopupEx_Opened;
hostWindow = null;
}
private void hostWindow_StateChanged(object sender, EventArgs e)
{
if (hostWindow != null && hostWindow.WindowState != WindowState.Minimized)
{
// special handling for validation popup
var target = PlacementTarget as FrameworkElement;
var holder = target != null ? target.DataContext as AdornedElementPlaceholder : null;
if (holder != null && holder.AdornedElement != null)
{
PopupAnimation = PopupAnimation.None;
IsOpen = false;
var errorTemplate = holder.AdornedElement.GetValue(Validation.ErrorTemplateProperty);
holder.AdornedElement.SetValue(Validation.ErrorTemplateProperty, null);
holder.AdornedElement.SetValue(Validation.ErrorTemplateProperty, errorTemplate);
}
}
}
private void hostWindow_SizeOrLocationChanged(object sender, EventArgs e)
{
RefreshPosition();
}
private void SetTopmostState(bool isTop)
{
isTop &= AllowTopMost;
// Don抰 apply state if it抯 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;
RECT rect;
if (!GetWindowRect(hwnd, out rect))
{
return;
}
//Debug.WriteLine("setting z-order " + isTop);
var left = rect.Left;
var top = rect.Top;
var width = rect.Width;
var height = rect.Height;
if (isTop)
{
SetWindowPos(hwnd, HWND_TOPMOST, left, top, width, height, SWP.TOPMOST);
}
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
SetWindowPos(hwnd, HWND_BOTTOM, left, top, width, height, SWP.TOPMOST);
SetWindowPos(hwnd, HWND_TOP, left, top, width, height, SWP.TOPMOST);
SetWindowPos(hwnd, HWND_NOTOPMOST, left, top, width, height, SWP.TOPMOST);
}
appliedTopMost = isTop;
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (CloseOnMouseLeftButtonDown)
{
IsOpen = false;
}
}
private Window hostWindow;
private bool? appliedTopMost;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
private static readonly IntPtr HWND_TOP = new IntPtr(0);
private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
/// <summary>
/// SetWindowPos options
/// </summary>
[Flags]
internal enum SWP
{
ASYNCWINDOWPOS = 0x4000,
DEFERERASE = 0x2000,
DRAWFRAME = 0x0020,
FRAMECHANGED = 0x0020,
HIDEWINDOW = 0x0080,
NOACTIVATE = 0x0010,
NOCOPYBITS = 0x0100,
NOMOVE = 0x0002,
NOOWNERZORDER = 0x0200,
NOREDRAW = 0x0008,
NOREPOSITION = 0x0200,
NOSENDCHANGING = 0x0400,
NOSIZE = 0x0001,
NOZORDER = 0x0004,
SHOWWINDOW = 0x0040,
TOPMOST = SWP.NOACTIVATE | SWP.NOOWNERZORDER | SWP.NOSIZE | SWP.NOMOVE | SWP.NOREDRAW | SWP.NOSENDCHANGING,
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal static int LOWORD(int i)
{
return (short)(i & 0xFFFF);
}
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SIZE
{
public int cx;
public int cy;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
private int _left;
private int _top;
private int _right;
private int _bottom;
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public void Offset(int dx, int dy)
{
_left += dx;
_top += dy;
_right += dx;
_bottom += dy;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Left
{
get { return _left; }
set { _left = value; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Right
{
get { return _right; }
set { _right = value; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Top
{
get { return _top; }
set { _top = value; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Bottom
{
get { return _bottom; }
set { _bottom = value; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Width
{
get { return _right - _left; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public int Height
{
get { return _bottom - _top; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public POINT Position
{
get { return new POINT { x = _left, y = _top }; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public SIZE Size
{
get { return new SIZE { cx = Width, cy = Height }; }
}
public static RECT Union(RECT rect1, RECT rect2)
{
return new RECT
{
Left = Math.Min(rect1.Left, rect2.Left),
Top = Math.Min(rect1.Top, rect2.Top),
Right = Math.Max(rect1.Right, rect2.Right),
Bottom = Math.Max(rect1.Bottom, rect2.Bottom),
};
}
public override bool Equals(object obj)
{
try
{
var rc = (RECT)obj;
return rc._bottom == _bottom
&& rc._left == _left
&& rc._right == _right
&& rc._top == _top;
}
catch (InvalidCastException)
{
return false;
}
}
public override int GetHashCode()
{
return (_left << 16 | LOWORD(_right)) ^ (_top << 16 | LOWORD(_bottom));
}
}
[SecurityCritical]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[DllImport("user32.dll", EntryPoint = "GetWindowRect", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[SecurityCritical]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool _SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SWP uFlags);
[SecurityCritical]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private static bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SWP uFlags)
{
if (!_SetWindowPos(hWnd, hWndInsertAfter, x, y, cx, cy, uFlags))
{
// If this fails it's never worth taking down the process. Let the caller deal with the error if they want.
return false;
}
return true;
}
#region P/Invoke imports & definitions
public static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32", EntryPoint = "SetWindowPos")]
internal static extern int SetWindowPos(IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags);
[DllImport("user32.dll")]
internal static extern IntPtr SetActiveWindow(IntPtr hWnd);
}
#endregion
}
}
主要功能点为在构造函数中添加事件:
EventManager.RegisterClassHandler(typeof(PopupEx), Popup.PreviewGotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnPreviewGotKeyboardFocus), true);
private static void OnPreviewGotKeyboardFocus(Object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.NewFocus as TextBoxBase;
if (textBox != null)
{
var hwndSource = PresentationSource.FromVisual(textBox) as HwndSource;
if (hwndSource != null)
{
NativeMethods.SetActiveWindow(hwndSource.Handle);
}
}
}
#region P/Invoke imports & definitions
public static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32", EntryPoint = "SetWindowPos")]
internal static extern int SetWindowPos(IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags);
[DllImport("user32.dll")]
internal static extern IntPtr SetActiveWindow(IntPtr hWnd);
}
#endregion
实现可拖动的popup,代码如下:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace PopupExControl
{
public class PopupEx : Popup
{
/// <summary>
/// 是否窗口随动,默认为随动(true)
/// </summary>
public bool IsPositionUpdate
{
get { return (bool)GetValue(IsPositionUpdateProperty); }
set { SetValue(IsPositionUpdateProperty, value); }
}
public static readonly DependencyProperty IsPositionUpdateProperty =
DependencyProperty.Register("IsPositionUpdate", typeof(bool), typeof(PopupEx), new PropertyMetadata(true, new PropertyChangedCallback(IsPositionUpdateChanged)));
private static void IsPositionUpdateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as PopupEx).pup_Loaded(d as PopupEx, null);
}
/// <summary>
/// 加载窗口随动事件
/// </summary>
public PopupEx()
{
//this.Loaded += pup_Loaded;
EventManager.RegisterClassHandler(typeof(PopupEx), Popup.PreviewGotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnPreviewGotKeyboardFocus),
true);
}
private static void OnPreviewGotKeyboardFocus(Object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.NewFocus as TextBoxBase;
if (textBox != null)
{
var hwndSource = PresentationSource.FromVisual(textBox) as HwndSource;
if (hwndSource != null)
{
NativeMethods.SetActiveWindow(hwndSource.Handle);
}
}
}
/// <summary>
/// 加载窗口随动事件
/// </summary>
private void pup_Loaded(object sender, RoutedEventArgs e)
{
Popup pup = sender as Popup;
var win = VisualTreeHelper.GetParent(pup);
while (win != null && (win as Window) == null)
{
win = VisualTreeHelper.GetParent(win);
}
if ((win as Window) != null)
{
(win as Window).LocationChanged -= PositionChanged;
if (IsPositionUpdate)
{
(win as Window).LocationChanged += PositionChanged;
}
}
}
/// <summary>
/// 刷新位置
/// </summary>
private void PositionChanged(object sender, EventArgs e)
{
var method = typeof(Popup).GetMethod("UpdatePosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (IsOpen)
{
method.Invoke(this, null);
}
}
//是否最前默认为非最前(false)
public static DependencyProperty TopmostProperty = Window.TopmostProperty.AddOwner(typeof(Popup), new FrameworkPropertyMetadata(false, OnTopmostChanged));
public bool Topmost
{
get { return (bool)GetValue(TopmostProperty); }
set { SetValue(TopmostProperty, value); }
}
private static void OnTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as PopupEx).UpdateWindow();
}
/// <summary>
/// 重写拉开方法,置于非最前
/// </summary>
/// <param name="e"></param>
protected override void OnOpened(EventArgs e)
{
UpdateWindow();
}
/// <summary>
/// 刷新Popup层级
/// </summary>
private void UpdateWindow()
{
var hwnd = ((HwndSource)PresentationSource.FromVisual(Child)).Handle;
RECT rect;
if (NativeMethods.GetWindowRect(hwnd, out rect))
{
NativeMethods.SetWindowPos(hwnd, Topmost ? -1 : -2, rect.Left, rect.Top, (int)Width, (int)Height, 0);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#region P/Invoke imports & definitions
public static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32", EntryPoint = "SetWindowPos")]
internal static extern int SetWindowPos(IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags);
[DllImport("user32.dll")]
internal static extern IntPtr SetActiveWindow(IntPtr hWnd);
}
#endregion
}
}
具体调用:
[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT lpPoint);
[DllImportAttribute("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IntPtr hWndPopup;
GetCursorPos(out POINT curPos);
hWndPopup = WindowFromPoint(curPos);
ReleaseCapture();
SendMessage(hWndPopup, WM_NCLBUTTONDOWN, new IntPtr(HT_CAPTION), IntPtr.Zero);
}
以此记录笔记。