#680 – IsHitTestVisible Applies to All Child Elements

If you set the IsHitTestVisible  property of a container element to false, it will turn off hit testing for all elements within that container.

Even if a child element sets its own IsHitTestVisible property to true, it will still be hidden from hit testing.  The false value set at the container level applies to all child elements, no matter what value they set.

    <StackPanel IsHitTestVisible="False">
        <Button Content="You can't Click this" HorizontalAlignment="Center" Margin="5" Click="Button_Click_1"/>
        <Button Content="You can't even Click this" HorizontalAlignment="Center" Margin="5" Click="Button_Click_2"
                IsHitTestVisible="True"/>
    </StackPanel>

Advertisements

#679 – Setting IsHitTestVisible to False Prevents Interaction with Controls

When you set the IsHitTestVisible property to false for a user interface element, it will no longer respond to mouse input and will not fire mouse-related events.

With IsHitTestVisible set to false, you therefore can’t use the mouse to interact with a user interface element at all.  In the example below, we can’t use the mouse to give the elements focus or to click or select any of them.

    <StackPanel>
        <Button Content="You can't Click this" HorizontalAlignment="Center" Margin="5" Click="Button_Click_1"
                IsHitTestVisible="False"/>
        <TextBox Text="You can't Edit this" HorizontalAlignment="Center" Margin="5"
                 IsHitTestVisible="False"/>
        <CheckBox Content="You can't Check this" HorizontalAlignment="Center" Margin="5"
                  IsHitTestVisible="False"/>
        <ComboBox HorizontalAlignment="Center" Margin="5" SelectedIndex="0"
                  IsHitTestVisible="False">
            <ComboBox.Items>
                <ComboBoxItem Content="You can't Select this"/>
                <ComboBoxItem Content="Or this"/>
            </ComboBox.Items>
        </ComboBox>
    </StackPanel>


Notice, however, that you can still interact with the elements using the keyboard.  For example, you can tab to the Button and then press Enter to trigger its Click event.

#678 – Hide an Element from the Mouse with IsHitTestVisible Property

You can completely hide a user interface element from the mouse by setting its IsHitTestVisible property to false.  When this property is false, no mouse related events will be fired for that control.

In the example below, one Label has its IsHitTestVisible property set to false.

    <StackPanel>
        <Label Content="Mouse Sees Me" Background="AliceBlue" Margin="10"
               MouseMove="Label_MouseMove_1"/>
        <Label Content="Mouse Doesn't see Me" Background="Beige" Margin="10"
               IsHitTestVisible="False"
               MouseMove="Label_MouseMove_2"/>
    </StackPanel>

Assume that we try to report the mouse position in both event handlers:

        private void ReportMouse(string element, MouseEventArgs e)
        {
            Point p = e.GetPosition(null);
            Console.WriteLine(string.Format("{0} sees mouse at ({1},{2})", element, p.X, p.Y));
        }

        private void Label_MouseMove_1(object sender, MouseEventArgs e)
        {
            ReportMouse("Label 1", e);
        }

        private void Label_MouseMove_2(object sender, MouseEventArgs e)
        {
            ReportMouse("Label 2", e);
        }

When we move the mouse over both labels at runtime, we only see output when it moves over label #1, which does not set IsHitTestVisible to false.

#677 – Why the Standard Mouse Wheel Delta is 120

For a standard Microsoft mouse, you’ll notice that the value of the MouseWheelEventArgs.Delta parameter is always either 120 when you turn the mouse wheel forward one click or -120 when you turn the mouse wheel backwards one click.  You might wonder why the value of 120 is used, rather than just 1 and -1 values.

The larger number for the mouse wheel delta is used for a typical mouse so that other manufacturers of mouse devices can include a mouse wheel with more precision, which could then return a smaller value for each delta.  For example, there might be twice as many click detents on a device, which could then return a delta value of 60.

The value of 120 can be evenly divided by 2, 3, 4 or 5.  This gives a mouse manufacturer more options in dividing up the current coarser clicks into an even number of higher resolution clicks.

#676 – MouseWheel Event Is Fired for Element That Mouse Pointer Is Over

When the user rotates the mouse wheel, the PreviewMouseWheel and MouseWheel events will fire.  The PreviewMouseWheel event tunnels down the logical tree, firing for the topmost element and working down to the element that the mouse pointer was located over when the mouse wheel was rotated.  The MouseWheel event then fires, bubbling up the logical tree and firing for each element until the topmost element is reached.

In the example below, if the user rotates the mouse wheel while the mouse pointer is over the Button, the event sequence is:

  • PreviewMouseWheel for Window
  • PreviewMouseWheel for StackPanel
  • PreviewMouseWheel for Button
  • MouseWheel for  Button
  • MouseWheel for StackPanel
  • MouseWheel for Window
<Window Name="win1" x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Mouse Wheel"
        Width="350" Height="200"
        MouseWheel="Window_MouseWheel" PreviewMouseWheel="Window_PreviewMouseWheel">

    <StackPanel MouseWheel="StackPanel_MouseWheel" PreviewMouseWheel="StackPanel_PreviewMouseWheel">
        <Label Content="Tiberius Julius Caesar Augustus" />
        <Button Content="Make Me Emperor" MouseWheel="Button_MouseWheel" PreviewMouseWheel="Button_PreviewMouseWheel"/>
    </StackPanel>
</Window>


#675 – Handling the PreviewMouseWheel and MouseWheel Events

Every user interface element defines the PreviewMouseWheel/MouseWheel event pair, which fire whenever the user interacts with the mouse wheel.

The PreviewMouseWheel event is a tunneling event, firing on each element of the logical tree, from the top down.  The corresponding MouseWheel event is bubbling, firing up the logical tree from the control where the event originated.

The mouse wheel events will fire once for each click of the mouse wheel.  The MouseWheelEventArgs.Delta property is meant to indicate the amount that the mouse wheel changed, but will in practice take on one of two values–a positive value when the mouse wheel is rotated forward (away from the user) and a negative value otherwise.

        private static int eventCount = 0;

        private void win1_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            Info = string.Format("Click #{0}, Delta = {1}", ++eventCount, e.Delta);
        }

Rotating the wheel forward, we get a Delta of 120.

Rotating backward, we get -120.

#674 – Mapping Mouse Position to Color, part II

In the previous post, we used the mouse position to map to the Hue and Saturation portions of an HSV color value and then set the background color of a window based on the mouse position.

This post adds to the example, by using the mouse wheel to control the Value portion of the color (which ranges from 0.0 to 1.0).

Here’s the updated XAML:

<Window Name="win1" x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Color from Mouse Position"
        SizeToContent="WidthAndHeight"
        MouseMove="win1_MouseMove_1" MouseWheel="win1_MouseWheel_1">

    <Canvas x:Name="canv1" Width="400" Height="400">
        <Label x:Name="lblInfo" Content="{Binding RGBInfo}" HorizontalAlignment="Center" />
        <Ellipse x:Name="ellipseCenter" Stroke="Black" Width="2" Height="2" Fill="Black" Canvas.Left="199" Canvas.Top="199"/>
    </Canvas>
</Window>

And the updated code-behind:

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        SolidColorBrush backBrush = new SolidColorBrush();
        double value = 1.0;

        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;
            win1.Background = backBrush;
        }

        private string rgbInfo;
        public string RGBInfo
        {
            get { return rgbInfo; }
            set
            {
                if (value != rgbInfo)
                {
                    rgbInfo = value;
                    RaisePropertyChanged("RGBInfo");
                }
            }
        }

        private void win1_MouseMove_1(object sender, MouseEventArgs e)
        {
            RecalcColor(e.GetPosition(canv1));
        }

        private void win1_MouseWheel_1(object sender, MouseWheelEventArgs e)
        {
            if ((e.Delta > 0) && (value < 1.0))
                value += 0.1;
            else if ((e.Delta < 0) && (value > 0.0))
                value -= 0.1;

            RecalcColor(Mouse.GetPosition(canv1));
        }

        private void RecalcColor(Point mousePos)
        {
            double radius = (canv1.ActualWidth / 2);

            double hue;
            double saturation;
            Color c = ColorFromMousePosition(mousePos, radius, out hue, out saturation);
            backBrush.Color = c;
            RGBInfo = string.Format("R={0}, G={1}, B={2}.  H={3:F1}, S={4:F1}, V={5:F1}",
                c.R, c.G, c.B,
                hue, saturation, value);

            if (value < 0.5)
            {
                lblInfo.Foreground = Brushes.White;
                ellipseCenter.Stroke = Brushes.White;
            }
            else
            {
                lblInfo.Foreground = Brushes.Black;
                ellipseCenter.Stroke = Brushes.Black;
            }
        }

        private Color ColorFromMousePosition(Point mousePos, double radius, out double hue, out double saturation)
        {
            // Position relative to center of canvas
            double xRel = mousePos.X - radius;
            double yRel = mousePos.Y - radius;

            // Hue is angle in deg, 0-360
            double angleRadians = Math.Atan2(yRel, xRel);
            hue = angleRadians * (180 / Math.PI);
            if (hue < 0)
                hue = 360 + hue;

            // Saturation is distance from center
            saturation = Math.Min(Math.Sqrt(xRel * xRel + yRel * yRel) / radius, 1.0);

            byte r, g, b;
            ColorUtil.HsvToRgb(hue, saturation, value, out r, out g, out b);
            return Color.FromRgb(r, g, b);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

(See the earlier post for the ColorUtil code).
We can now roll the mouse wheel down to reduce the value, making the colors darker.