#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>

Advertisement

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

#673 – Mapping Mouse Position to Color

You can map a 2D mouse position to a color by using the mouse position to map to the Hue and Saturation and then converting the HSV value to an RGB value.  (We just use 1.0 as the Value component of HSV).  We can map the mouse to a color wheel and use the angle of a line from the mouse position to the center of the wheel as the hue and use the distance from the center of the wheel as the saturation.

So we want to map a mouse position against a color wheel like the following:

In WPF, we’ll just create a Canvas, with a Label that will be used to dump out the RGB values.

<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">

    <Canvas x:Name="canv1" Width="400" Height="400">
        <Label Content="{Binding RGBInfo}" HorizontalAlignment="Center" />
    </Canvas>
</Window>

In the code-behind, we’ll handle the MouseMove event for the window and re-calculate the color of the window’s background brush, based on the mouse position.

    /// <summary>
    ///
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        SolidColorBrush backBrush = new SolidColorBrush();

        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)
        {
            double radius = (canv1.ActualWidth / 2);

            Color c = ColorFromMousePosition(e.GetPosition(canv1), radius);
            backBrush.Color = c;
            RGBInfo = string.Format("R={0}, G={1}, B={2}", c.R, c.G, c.B);
        }

        Color ColorFromMousePosition(Point mousePos, double radius)
        {
            // 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);
            double hue = angleRadians * (180 / Math.PI);
            if (hue < 0)
                hue = 360 + hue;

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

            byte r, g, b;
            ColorUtil.HsvToRgb(hue, saturation, 1.0, 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));
        }
    }

Notice that we make use of an HSV to RGB conversion function, found at http://www.splinter.com.au/converting-hsv-to-rgb-colour-using-c/. Below is the function as we’re using it.

    public static class ColorUtil
    {
        /// <summary>
        /// Convert HSV to RGB
        /// h is from 0-360
        /// s,v values are 0-1
        /// r,g,b values are 0-255
        /// Based upon http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space#HSV_Transformation_C_.2F_C.2B.2B_Code_2
        /// </summary>
        public static void HsvToRgb(double h, double S, double V, out byte r, out byte g, out byte b)
        {
            // ######################################################################
            // T. Nathan Mundhenk
            // mundhenk@usc.edu
            // C/C++ Macro HSV to RGB

            double H = h;
            while (H < 0) { H += 360; };
            while (H >= 360) { H -= 360; };
            double R, G, B;
            if (V <= 0)
            { R = G = B = 0; }
            else if (S <= 0)
            {
                R = G = B = V;
            }
            else
            {
                double hf = H / 60.0;
                int i = (int)Math.Floor(hf);
                double f = hf - i;
                double pv = V * (1 - S);
                double qv = V * (1 - S * f);
                double tv = V * (1 - S * (1 - f));
                switch (i)
                {

                    // Red is the dominant color

                    case 0:
                        R = V;
                        G = tv;
                        B = pv;
                        break;

                    // Green is the dominant color

                    case 1:
                        R = qv;
                        G = V;
                        B = pv;
                        break;
                    case 2:
                        R = pv;
                        G = V;
                        B = tv;
                        break;

                    // Blue is the dominant color

                    case 3:
                        R = pv;
                        G = qv;
                        B = V;
                        break;
                    case 4:
                        R = tv;
                        G = pv;
                        B = V;
                        break;

                    // Red is the dominant color

                    case 5:
                        R = V;
                        G = pv;
                        B = qv;
                        break;

                    // Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.

                    case 6:
                        R = V;
                        G = tv;
                        B = pv;
                        break;
                    case -1:
                        R = V;
                        G = pv;
                        B = qv;
                        break;

                    // The color is not defined, we should throw an error.

                    default:
                        //LFATAL("i Value error in Pixel conversion, Value is %d", i);
                        R = G = B = V; // Just pretend its black/white
                        break;
                }
            }
            r = Clamp((byte)(R * 255.0));
            g = Clamp((byte)(G * 255.0));
            b = Clamp((byte)(B * 255.0));
        }

        /// <summary>
        /// Clamp a value to 0-255
        /// </summary>
        private static byte Clamp(byte i)
        {
            if (i < 0) return 0;
            if (i > 255) return 255;
            return i;
        }
    }

The final result is a window whose background color changes as we move the mouse around.

#672 – Mouse Coordinates Are in Device Independent Units

As with other screen position values in WPF, the Mouse.GetPosition function returns coordinates that are in device independent units, rather than in pixels.

1 device independet unit = 1/96 in.  This means that on a 96 dpi device, the value will actually map to pixels, but on other devices, the value will not be the same as the # pixels.

 

 

#671 – Mouse.GetPosition Only Works When Mouse Is in Window

You typically only use the Mouse.GetPosition method when the mouse pointer is within the boundaries of your application’s main window.  When the mouse is located outside of the window, calling GetPosition with a null value will return 0 values for the mouses X and Y position, regardless of where the mouse is located on the screen.

In the example below, we are updating a label to dump out the value returned by Mouse.GetPosition when a timer fires, ever second.