Thursday, January 6, 2022

RenderLayout or Margin bug?

I have a requirement to host a control in the content area of a tab control, but display it to the right of the tabs. I though this would be easy with a negative Margin or a RenderLayout. It worked perfectly, until I tried to interact with the control. It's acting as if there is a transparent panel between the tabs and the right margin of the control that still allows the control to be visible, but traps all mouse clicks. If you write a TabControl.MouseDown event handler you can see it being triggered, even when you click on the upper half of the button below.

<Window x:Class="RenderTransformBug.MainWindow"
        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"
        xmlns:local="clr-namespace:RenderTransformBug"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <TabControl>
        <TabItem Header="Tab Item">
            <Button Height="30" Width="100" Content="Click me" Cursor="Hand"
                    VerticalAlignment="Top" Margin="0,-20,0,0"/>
        </TabItem>
    </TabControl>
</Window>

You can see that the button no longer realizes the mouse is over it when I move the cursor outside the Content area of the TabItem.



One solution is to detect when the mouse is over the button and capture the mouse. When the mouse is no longer over the button we need to release the mouse. This means we have to evaluate the location of the mouse whenever it moves. We cannot do this in the CanExecute event handler because that doesn't get called for mouse movements, so we need to write a MouseMove event handler for the window. Take a look at the XAML below and the supporting code.

<Window x:Class="RenderTransformBug.MainWindow"
        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"
        xmlns:local="clr-namespace:RenderTransformBug"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        MouseMove="Window_MouseMove"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="ClickCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource ClickCommand}" Executed="Click_Executed"/>
    </Window.CommandBindings>
    <TabControl>
        <TabItem Header="Tab Item">
            <Button Height="30" Width="100" Content="Click me" Cursor="Hand" Initialized="Button_Initialized"
                    VerticalAlignment="Top" Margin="0,-20,0,0" Command="{StaticResource ClickCommand}"/>
        </TabItem>
    </TabControl>
</Window>

 

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
 
namespace RenderTransformBug
{
    public partial class MainWindow : Window
    {
        Button b;
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Click_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Clicked");
        }
 
        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            Point P = Mouse.GetPosition(b);
 
            if (P.X >= 0 && P.X <= b.ActualWidth && P.Y >= 0 && P.Y <= b.ActualHeight)
                b.CaptureMouse();
            else
                b.ReleaseMouseCapture();
        }
 
        private void Button_Initialized(object sender, System.EventArgs e)
        {
            b = (Button)sender;
        }
    }
}

Note that Button.IsMouseOver returns false until the button has captured the mouse so we have to look at the button's coordinates. This now gives us the desired functionality.






No comments:

Post a Comment