I have a simple example to show this problem. There are two Window
which one of them is MainWindow and the other is SecondWindow. I put a large button inside SecondWindow on the bottom and the button has a IsMouseOver
trigger. But it doesn't work correctly when cursor moves. I used the code below to create this whole example. Try it and see the problem. How can I fix it?
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();
}
}
Problem image: The cursor over the MainWindow, not the SecondWindow, but background color of the button doesn't change to blue, it is still red.
It is quite a challenge because it seems that AllowsTransparency="True" disables all normal behavoir for mouse related to underlying windows etc.
As a workaround I tried to make a subclass of Button to do the trick as shown below:
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();
}
});
}
}
}
}
}
And the corresponding XAML looks like this:
<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>
This is a rather hefty workaround for such a small result. At least it works on my computer, so i hope it works for you as well.