wpftriggersismouseover

Why IsMouseOver Trigger doesn't work despite cursor is not over the button?


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.

enter image description here


Solution

  • 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.