0

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

Ali Tor
  • 2,772
  • 2
  • 27
  • 58
  • It's because when the mouse leaves the button horizontally it wil not hit the buttons immediate parent because the window is frameless and the button has no margin. In the context of SecondWindow is has then not left the button and the trigger is not executed. If you set a Margin on the button with some width it will work - but maybe not optimally for you? –  Sep 17 '16 at 14:33
  • But it should work without setting margin. Also I tried it before but unless the margin is large, it is not sensitive. Do you think this is probably a bug or a forgotten thing to fix by language designers? – Ali Tor Sep 17 '16 at 15:13
  • @AliTor your code should, and does, work fine for me – dkozl Sep 17 '16 at 18:04
  • @dkozl, I can't understand why it happens. It didn't work anytime for me. Are you sure you have the same codes? – Ali Tor Sep 17 '16 at 19:52
  • Yes. Copied and pasted your code as is and button is red only when mouse is over it. Also I'm not aware of any bug in this region. – dkozl Sep 17 '16 at 20:07
  • So, what may be the problem for me? In IDE or anything else? – Ali Tor Sep 17 '16 at 20:32
  • Possible duplicate of [IsMouseOver trigger doesn't work when using ShowDialog and borderless window](http://stackoverflow.com/questions/39258393/ismouseover-trigger-doesnt-work-when-using-showdialog-and-borderless-window) – Grx70 Sep 18 '16 at 07:12

1 Answers1

1

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.

  • Your solution is working. But I was expecting a shorter way without creating a custom control. Anyway, this is the best idea unless a much better comes. Thanks for your effort @Henrik. – Ali Tor Sep 18 '16 at 19:01