我试图检测鼠标何时进入VS 2017标题栏,但我发现MouseEnter
和MouseLeave
事件无法正常工作。只有当鼠标进入下面屏幕截图中绿色矩形所示的子控件时,才会触发事件。
标题栏是DockPanel
,其中包含一些元素。我已将其背景设置为SolidColorBrush(Colors.Red)
,以确保点击测试正确运行。当鼠标悬停在绿色矩形IsMouseOver
中的元素时,正确返回true,但在其他任何地方都是false。对于菜单栏,IsMouseOver
和MouseEnter
以及MouseLeave
事件正常工作。那可能有什么问题?
更新2:标题栏很可能被标记为非客户区域,这就是导致此问题的原因
更新
这是主VS窗口的Visual Tree:
已解压缩MainWindowTitleBar
类:
using Microsoft.VisualStudio.PlatformUI.Shell.Controls;
using Microsoft.VisualStudio.Shell;
using System;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace Microsoft.VisualStudio.PlatformUI
{
public sealed class MainWindowTitleBar : Border, INonClientArea
{
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
int INonClientArea.HitTest(Point point)
{
return 2;
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new MainWindowTitleBarAutomationPeer(this);
}
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
if (!e.Handled)
{
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
CustomChromeWindow.ShowWindowMenu(hwndSource, this, Mouse.GetPosition(this), base.RenderSize);
}
e.Handled = true;
}
}
}
}
为MainWindowTitleBar
提取XAML:
<mwtb:MainWindowTitleBar Name="MainWindowTitleBar" x:Uid="vs:MainWindowTitleBar_1" Grid.Row="0" Grid.Column="0" Background="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowActiveCaptionBrushKey}}" TextElement.Foreground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowActiveCaptionTextBrushKey}}">
<DockPanel x:Uid="DockPanel_2">
<wcp:SystemMenu Name="SystemMenu" x:Uid="Image_1" Source="{TemplateBinding Window.Icon}" VectorFill="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowActiveIconDefaultBrushKey}}" Width="32" Height="27" Margin="0,0,12,4" Padding="12,7,0,0" DockPanel.Dock="Left" VectorIcon="{Binding Source={x:Static Application.Current}, Path=VectorIcon}" />
<StackPanel Name="WindowTitleBarButtons" x:Uid="WindowTitleBarButtons" Orientation="Horizontal" DockPanel.Dock="Right">
<wcp:WindowTitleBarButton Name="MinimizeButton" x:Uid="MinimizeButton" VerticalAlignment="Top" Command="{x:Static vsc:ViewCommands.MinimizeWindow}" BorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveBorderBrushKey}}" BorderThickness="1,0,1,1" GlyphForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveGlyphBrushKey}}" HoverBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBrushKey}}" HoverBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBorderBrushKey}}" HoverForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveGlyphBrushKey}}" HoverBorderThickness="1,0,1,1" PressedBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBrushKey}}" PressedBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBorderBrushKey}}" PressedForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownGlyphBrushKey}}" PressedBorderThickness="1,0,1,1" Padding="0,3,0,0" Width="34" Height="26" AutomationProperties.Name="Minimize" AutomationProperties.AutomationId="Minimize" ToolTip="{x:Static vs:MainWindowResources.WindowMinimizeToolTip}" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Path Name="MinimizeButtonPath" x:Uid="MinimizeButtonPath" Width="9" Height="9" Stretch="None" Data="F1M0,6L0,9 9,9 9,6 0,6z" Fill="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource Self}}" />
</wcp:WindowTitleBarButton>
<wcp:WindowTitleBarButton Name="MaximizeRestoreButton" x:Uid="MaximizeRestoreButton" VerticalAlignment="Top" Command="{x:Static vsc:ViewCommands.ToggleMaximizeRestoreWindow}" BorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveBorderBrushKey}}" BorderThickness="1,0,1,1" GlyphForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveGlyphBrushKey}}" HoverBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBrushKey}}" HoverBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBorderBrushKey}}" HoverForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveGlyphBrushKey}}" HoverBorderThickness="1,0,1,1" PressedBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBrushKey}}" PressedBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBorderBrushKey}}" PressedForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownGlyphBrushKey}}" PressedBorderThickness="1,0,1,1" Padding="0,3,0,0" Width="34" Height="26" AutomationProperties.Name="Maximize" AutomationProperties.AutomationId="Maximize" ToolTip="{x:Static vs:MainWindowResources.WindowMaximizeToolTip}" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Path Name="MaximizeRestoreButtonPath" x:Uid="MaximizeRestoreButtonPath" Width="9" Height="9" Stretch="Uniform" Data="F1M0,0L0,9 9,9 9,0 0,0 0,3 8,3 8,8 1,8 1,3z" Fill="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource Self}}" />
</wcp:WindowTitleBarButton>
<wcp:WindowTitleBarButton Name="HideButton" x:Uid="HideButton" VerticalAlignment="Top" Command="{x:Static vsc:ViewCommands.CloseWindow}" BorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveBorderBrushKey}}" BorderThickness="1,0,1,1" GlyphForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonActiveGlyphBrushKey}}" HoverBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBrushKey}}" HoverBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveBorderBrushKey}}" HoverForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonHoverActiveGlyphBrushKey}}" HoverBorderThickness="1,0,1,1" PressedBackground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBrushKey}}" PressedBorderBrush="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownBorderBrushKey}}" PressedForeground="{DynamicResource {x:Static ui:EnvironmentColors.MainWindowButtonDownGlyphBrushKey}}" PressedBorderThickness="1,0,1,1" Padding="0,3,0,0" Width="34" Height="26" AutomationProperties.Name="Close" AutomationProperties.AutomationId="Close" ToolTip="{x:Static vs:MainWindowResources.WindowCloseToolTip}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<Path Name="HideButtonPath" x:Uid="HideButtonPath" Width="10" Height="8" Stretch="Uniform" Data="F1M0,0L2,0 5,3 8,0 10,0 6,4 10,8 8,8 5,5 2,8 0,8 4,4 0,0z" Fill="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource Self}}" />
</wcp:WindowTitleBarButton>
</StackPanel>
<mwtb:FrameControlContainer Name="PART_TitleBarFrameControlContainer" x:Uid="PART_TitleBarFrameControlContainer" DockPanel.Dock="Right" TextElement.FontSize="{DynamicResource VsFont.EnvironmentFontSize}" TextElement.FontFamily="{DynamicResource VsFont.EnvironmentFontFamily}" Margin="0,0,2,0" DataContext="{Binding FrameControls}" />
<TextBlock x:Uid="TextBlock_1" Text="{TemplateBinding Window.Title}" TextBlock.FontFamily="{DynamicResource VsFont.CaptionFontFamily}" TextBlock.FontSize="{DynamicResource VsFont.CaptionFontSize}" TextBlock.FontWeight="{DynamicResource VsFont.CaptionFontWeight}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" Margin="0,7,0,4" />
</DockPanel>
</mwtb:MainWindowTitleBar>
答案 0 :(得分:1)
我的原始答案能够检测非客户端鼠标移动,但不能在鼠标离开时检测到。根据OP发现需要使用TrackMouseEvent
来执行此操作,我已更新了我的答案,以显示功能完备的示例。
如评论中所述,WPF不处理/包装非客户区事件。我找不到解释为什么。虽然可以使用消息钩检测鼠标移动(因此鼠标输入)。
在VS扩展中,挂钩启动时使用:
IntPtr vsHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
HwndSource source = HwndSource.FromHwnd(vsHandle);
source.AddHook(new HwndSourceHook(WndProc));
支持代码是:
private const int WM_NCHITTEST = 0x0084;
private const int WM_NCMOUSEMOVE = 0x00a0;
private const int WM_NCMOUSELEAVE = 0x02a2;
private enum HtResult
{
HTERROR = (-2),
HTTRANSPARENT = (-1),
HTNOWHERE = 0,
HTCLIENT = 1,
HTCAPTION = 2,
HTSYSMENU = 3,
HTGROWBOX = 4,
HTSIZE = HTGROWBOX,
HTMENU = 5,
HTHSCROLL = 6,
HTVSCROLL = 7,
HTMINBUTTON = 8,
HTMAXBUTTON = 9,
HTLEFT = 10,
HTRIGHT = 11,
HTTOP = 12,
HTTOPLEFT = 13,
HTTOPRIGHT = 14,
HTBOTTOM = 15,
HTBOTTOMLEFT = 16,
HTBOTTOMRIGHT = 17,
HTBORDER = 18,
HTREDUCE = HTMINBUTTON,
HTZOOM = HTMAXBUTTON,
HTSIZEFIRST = HTLEFT,
HTSIZELAST = HTBOTTOMRIGHT,
HTOBJECT = 19,
HTCLOSE = 20,
HTHELP = 21
}
[Flags]
public enum TMEFlags : uint
{
TME_CANCEL = 0x80000000,
TME_HOVER = 0x00000001,
TME_LEAVE = 0x00000002,
TME_NONCLIENT = 0x00000010,
TME_QUERY = 0x40000000
}
[DllImport("user32.dll")]
static extern int TrackMouseEvent(ref TRACKMOUSEEVENT lpEventTrack);
[StructLayout(LayoutKind.Sequential)]
public struct TRACKMOUSEEVENT
{
public int cbSize;
public TMEFlags dwFlags;
public IntPtr hwndTrack;
public int dwHoverTime;
}
private bool _trackingMouseMove;
private TRACKMOUSEEVENT _tme;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_NCMOUSEMOVE)
{
if (!_trackingMouseMove)
{
_tme = new TRACKMOUSEEVENT();
_tme.hwndTrack = hwnd;
_tme.cbSize = Marshal.SizeOf(typeof(TRACKMOUSEEVENT));
_tme.dwFlags = TMEFlags.TME_NONCLIENT | TMEFlags.TME_LEAVE;
int success = TrackMouseEvent(ref _tme);
_trackingMouseMove = (success != 0);
}
var hitTestResult = (HtResult)wParam;
if ((hitTestResult == HtResult.HTSYSMENU) || (hitTestResult == HtResult.HTCAPTION))
{
// Raise event here
System.Diagnostics.Debug.WriteLine("Mouse over title bar");
}
}
else if (msg == WM_NCMOUSELEAVE)
{
_trackingMouseMove = false;
System.Diagnostics.Debug.WriteLine("Mouse left the title bar");
}
return IntPtr.Zero;
}