为什么 IsMouseOver 触发器不工作尽管光标不在按钮上?
Why IsMouseOver Trigger doesn't work despite cursor is not over the button?
我有一个简单的例子来说明这个问题。有两个Window
,其中一个是MainWindow,另一个是SecondWindow。我在底部的 SecondWindow 中放了一个大按钮,该按钮有一个 IsMouseOver
触发器。但是当光标移动时它不能正常工作。我使用下面的代码来创建整个示例。尝试一下,看看问题所在。我该如何解决?
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" AllowsTransparency="True" WindowStyle="None">
<Grid>
<Button Content="Show Dialog" HorizontalAlignment="Left" Margin="10,71,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" RenderTransformOrigin="-1.211,0.918"/>
</Grid>
SecondWindow.xaml
<Window x:Class="WpfApplication3.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="Green" AllowsTransparency="True" WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="SAVE" Height="50" VerticalAlignment="Bottom">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SecondWindow w = new SecondWindow();
w.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
w.Owner = this;
w.ShowDialog();
}
}
问题图片:光标在MainWindow上,不是SecondWindow上,但是按钮的背景颜色没有变成蓝色,还是红色。
这是一个很大的挑战,因为 AllowsTransparency="True" 似乎禁用了与底层 windows 等相关的鼠标的所有正常行为
作为一种解决方法,我尝试创建一个 Button 的子类来实现如下所示的技巧:
按钮Class:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SO39547486
{
public class MagicButton : Button, IDisposable
{
System.Timers.Timer m_colorTimer;
DateTime m_lastMouseMove;
Brush m_tmpBackground;
Brush m_tmpForeground;
public MagicButton()
{
MouseMove += MagicButton_MouseMove;
}
~MagicButton()
{
Dispose();
}
public Brush FocusBackground
{
get { return (Brush)GetValue(FocusBackgroundProperty); }
set { SetValue(FocusBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for FocusBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FocusBackgroundProperty =
DependencyProperty.Register("FocusBackground", typeof(Brush), typeof(MagicButton), new PropertyMetadata(Brushes.Magenta));
public Brush FocusForeground
{
get { return (Brush)GetValue(FocusForegroundProperty); }
set { SetValue(FocusForegroundProperty, value); }
}
// Using a DependencyProperty as the backing store for FocusForeground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FocusForegroundProperty =
DependencyProperty.Register("FocusForeground", typeof(Brush), typeof(MagicButton), new PropertyMetadata(Brushes.White));
private void CleanupTimer()
{
if (m_colorTimer != null)
{
m_colorTimer.Stop();
m_colorTimer.Dispose();
m_colorTimer = null;
}
}
public void Dispose()
{
CleanupTimer();
}
private void MagicButton_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (m_colorTimer == null)
{
m_colorTimer = new System.Timers.Timer(50);
m_colorTimer.Elapsed += ColorTimer_Elapsed;
m_colorTimer.Start();
m_tmpBackground = Background;
Background = FocusBackground;
m_tmpForeground = Foreground;
Foreground = FocusForeground;
}
var point = e.GetPosition(this);
m_lastMouseMove = DateTime.Now;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
private struct Win32Point
{
public Int32 X;
public Int32 Y;
};
private static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
private bool IsCursorOverMe()
{
var cursorPos = GetMousePosition();
var topLeft = PointToScreen(new Point(0, 0));
Rect bounds = new Rect(topLeft.X, topLeft.Y, ActualWidth, ActualHeight);
return bounds.Contains(cursorPos);
}
private void ColorTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (m_colorTimer != null)
{
var duration = DateTime.Now - m_lastMouseMove;
if (duration.TotalMilliseconds < 100)
{
Dispatcher.Invoke(() =>
{
if (!IsCursorOverMe())
{
Background = m_tmpBackground;
Foreground = m_tmpForeground;
CleanupTimer();
}
});
}
}
}
}
}
对应的XAML是这样的:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="SO39547486.SecondWindow"
xmlns:local="clr-namespace:SO39547486"
Title="Window1" Height="300" Width="300"
Background="Green" AllowsTransparency="True" WindowStyle="None">
<Grid>
<local:MagicButton
Content="SAVE"
x:Name="SaveCmd"
Height="50"
VerticalAlignment="Bottom"
FocusBackground="Red"
FocusForeground="White"
>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
</Style>
</Button.Style>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" UseLayoutRounding="True" d:DesignUseLayoutRounding="True">
<ContentPresenter
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
HorizontalAlignment="Center"
UseLayoutRounding="True"
VerticalAlignment="Center"
d:DesignUseLayoutRounding="True"/>
</Border>
</ControlTemplate>
</Button.Template>
</local:MagicButton>
</Grid>
</Window>
对于如此小的结果,这是一个相当繁重的解决方法。至少它在我的电脑上有效,所以我希望它也适用于你。
我有一个简单的例子来说明这个问题。有两个Window
,其中一个是MainWindow,另一个是SecondWindow。我在底部的 SecondWindow 中放了一个大按钮,该按钮有一个 IsMouseOver
触发器。但是当光标移动时它不能正常工作。我使用下面的代码来创建整个示例。尝试一下,看看问题所在。我该如何解决?
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" AllowsTransparency="True" WindowStyle="None">
<Grid>
<Button Content="Show Dialog" HorizontalAlignment="Left" Margin="10,71,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" RenderTransformOrigin="-1.211,0.918"/>
</Grid>
SecondWindow.xaml
<Window x:Class="WpfApplication3.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="Green" AllowsTransparency="True" WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="SAVE" Height="50" VerticalAlignment="Bottom">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SecondWindow w = new SecondWindow();
w.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
w.Owner = this;
w.ShowDialog();
}
}
问题图片:光标在MainWindow上,不是SecondWindow上,但是按钮的背景颜色没有变成蓝色,还是红色。
这是一个很大的挑战,因为 AllowsTransparency="True" 似乎禁用了与底层 windows 等相关的鼠标的所有正常行为
作为一种解决方法,我尝试创建一个 Button 的子类来实现如下所示的技巧:
按钮Class:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SO39547486
{
public class MagicButton : Button, IDisposable
{
System.Timers.Timer m_colorTimer;
DateTime m_lastMouseMove;
Brush m_tmpBackground;
Brush m_tmpForeground;
public MagicButton()
{
MouseMove += MagicButton_MouseMove;
}
~MagicButton()
{
Dispose();
}
public Brush FocusBackground
{
get { return (Brush)GetValue(FocusBackgroundProperty); }
set { SetValue(FocusBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for FocusBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FocusBackgroundProperty =
DependencyProperty.Register("FocusBackground", typeof(Brush), typeof(MagicButton), new PropertyMetadata(Brushes.Magenta));
public Brush FocusForeground
{
get { return (Brush)GetValue(FocusForegroundProperty); }
set { SetValue(FocusForegroundProperty, value); }
}
// Using a DependencyProperty as the backing store for FocusForeground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FocusForegroundProperty =
DependencyProperty.Register("FocusForeground", typeof(Brush), typeof(MagicButton), new PropertyMetadata(Brushes.White));
private void CleanupTimer()
{
if (m_colorTimer != null)
{
m_colorTimer.Stop();
m_colorTimer.Dispose();
m_colorTimer = null;
}
}
public void Dispose()
{
CleanupTimer();
}
private void MagicButton_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (m_colorTimer == null)
{
m_colorTimer = new System.Timers.Timer(50);
m_colorTimer.Elapsed += ColorTimer_Elapsed;
m_colorTimer.Start();
m_tmpBackground = Background;
Background = FocusBackground;
m_tmpForeground = Foreground;
Foreground = FocusForeground;
}
var point = e.GetPosition(this);
m_lastMouseMove = DateTime.Now;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
private struct Win32Point
{
public Int32 X;
public Int32 Y;
};
private static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
private bool IsCursorOverMe()
{
var cursorPos = GetMousePosition();
var topLeft = PointToScreen(new Point(0, 0));
Rect bounds = new Rect(topLeft.X, topLeft.Y, ActualWidth, ActualHeight);
return bounds.Contains(cursorPos);
}
private void ColorTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (m_colorTimer != null)
{
var duration = DateTime.Now - m_lastMouseMove;
if (duration.TotalMilliseconds < 100)
{
Dispatcher.Invoke(() =>
{
if (!IsCursorOverMe())
{
Background = m_tmpBackground;
Foreground = m_tmpForeground;
CleanupTimer();
}
});
}
}
}
}
}
对应的XAML是这样的:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="SO39547486.SecondWindow"
xmlns:local="clr-namespace:SO39547486"
Title="Window1" Height="300" Width="300"
Background="Green" AllowsTransparency="True" WindowStyle="None">
<Grid>
<local:MagicButton
Content="SAVE"
x:Name="SaveCmd"
Height="50"
VerticalAlignment="Bottom"
FocusBackground="Red"
FocusForeground="White"
>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
</Style>
</Button.Style>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" UseLayoutRounding="True" d:DesignUseLayoutRounding="True">
<ContentPresenter
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
HorizontalAlignment="Center"
UseLayoutRounding="True"
VerticalAlignment="Center"
d:DesignUseLayoutRounding="True"/>
</Border>
</ControlTemplate>
</Button.Template>
</local:MagicButton>
</Grid>
</Window>
对于如此小的结果,这是一个相当繁重的解决方法。至少它在我的电脑上有效,所以我希望它也适用于你。