首页 > 解决方案 > WPF - 如何通过半透明图层创建点击

问题描述

我想要这样的屏幕录制软件。

在此处输入图像描述 我的示例 wpf 窗口如下所示

<Window x:Class="WpfTestApp.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:WpfTestApp"
    mc:Ignorable="d"
    ShowInTaskbar="False" WindowStyle="None" ResizeMode="NoResize"
    AllowsTransparency="True" 
    UseLayoutRounding="True"
    Opacity="1" 
    Cursor="ScrollAll" 
    Topmost="True"
    WindowState="Maximized"
    >
<Window.Background>
    <SolidColorBrush Color="#01ffffff" Opacity="0" />
</Window.Background>

<Grid>

    <Canvas x:Name="canvas1">
        <Path Fill="#CC000000" Cursor="Cross" x:Name="backgroundPath">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <RectangleGeometry Rect="0,0,1440,810"/>
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <RectangleGeometry Rect="300,200,800,300" />
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
    </Canvas>

</Grid>

现在的问题是我不能让半透明区域backgroundPath点击通过。我已将其IsHitTestVisible属性设置为 false,但仍然没有更改。我曾经SetWindowLong使整个窗口透明,这让我可以点击窗口,但是我的窗口和其中的控件的所有事件都不起作用。

任何人都可以建议我如何实现这一目标?

标签: wpfclick-through

解决方案


我实际上对此感到好奇,看起来并没有真正的“适当”或“官方”方式来实现仅在窗口而不是控件上的透明度。

取而代之的是,我想出了一个功能有效的解决方案:

MainWindow XAML(我刚刚添加了一个按钮)

<Window x:Class="test.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:test"
        mc:Ignorable="d"
        Title="MainWindow"
        WindowStyle="None"
        AllowsTransparency="True"
        ShowInTaskbar="False" 
        ResizeMode="NoResize"
        UseLayoutRounding="True"
        Opacity="1" 
        Cursor="ScrollAll" 
        Topmost="True"
        WindowState="Maximized">
    <Window.Background>
        <SolidColorBrush Color="#01ffffff" Opacity="0" />
    </Window.Background>
    <Grid>
        <Canvas x:Name="canvas1">
            <Path Fill="#CC000000" Cursor="Cross" x:Name="backgroundPath">
                <Path.Data>
                    <CombinedGeometry GeometryCombineMode="Exclude">
                        <CombinedGeometry.Geometry1>
                            <RectangleGeometry Rect="0,0,1440,810"/>
                        </CombinedGeometry.Geometry1>
                        <CombinedGeometry.Geometry2>
                            <RectangleGeometry Rect="300,200,800,300" />
                        </CombinedGeometry.Geometry2>
                    </CombinedGeometry>
                </Path.Data>
            </Path>
        </Canvas>

        <Button x:Name="My_Button" Width="100" Height="50" Background="White" IsHitTestVisible="True" HorizontalAlignment="Center" VerticalAlignment="Top" Click="Button_Click"/>
    </Grid>
</Window>

主窗口 C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Threading;

namespace test
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        const int WS_EX_TRANSPARENT = 0x00000020;
        const int GWL_EXSTYLE = (-20);
        public const uint WS_EX_LAYERED = 0x00080000;

        [DllImport("user32.dll")]
        static extern int GetWindowLong(IntPtr hwnd, int index);

        [DllImport("user32.dll")]
        static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

        [StructLayout(LayoutKind.Sequential)]
        internal struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };

        private bool _isClickThrough = true;

        public MainWindow()
        {
            InitializeComponent();

            // List of controls to make clickable. I'm just adding my button.
            List<System.Windows.Controls.Control> controls = new List<System.Windows.Controls.Control>();
            controls.Add(My_Button);

            Thread globalMouseListener = new Thread(() =>
            {
                while (true)
                {
                    Point p1 = GetMousePosition();
                    bool mouseInControl = false;

                    for (int i = 0; i < controls.Count; i++)
                    {
                        Point p2 = new Point();
                        Rect r = new Rect();

                        System.Windows.Controls.Control iControl = controls[i];

                        Dispatcher.BeginInvoke(new Action(() =>
                        {
                            // Get control position relative to window
                            p2 = iControl.TransformToAncestor(this).Transform(new Point(0, 0));

                            // Add window position to get global control position
                            r.X = p2.X + this.Left;
                            r.Y = p2.Y + this.Top;

                            // Set control width/height
                            r.Width = iControl.Width;
                            r.Height = iControl.Height;

                            if (r.Contains(p1))
                            {
                                mouseInControl = true;
                            }

                            if (mouseInControl && _isClickThrough)
                            {
                                _isClickThrough = false;

                                var hwnd = new WindowInteropHelper(this).Handle;
                                SetWindowExNotTransparent(hwnd);
                            }
                            else if (!mouseInControl && !_isClickThrough)
                            {
                                _isClickThrough = true;

                                var hwnd = new WindowInteropHelper(this).Handle;
                                SetWindowExTransparent(hwnd);
                            }
                        }));
                    }

                    Thread.Sleep(15);
                }
            });

            globalMouseListener.Start();
        }

        public static Point GetMousePosition()
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }

        public static void SetWindowExTransparent(IntPtr hwnd)
        {
            var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
            SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
        }

        public static void SetWindowExNotTransparent(IntPtr hwnd)
        {
            var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
            SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle & ~WS_EX_TRANSPARENT);
        }

        private void Button_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.MessageBox.Show("hey it worked");
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            var hwnd = new WindowInteropHelper(this).Handle;
            SetWindowExTransparent(hwnd);
        }
    }
}

基本上,如果鼠标悬停在控件上,我会调用SetWindowExNotTransparent将其变为正常的非点击窗口。如果鼠标不在控件上,它会使用 将其切换回单击状态SetWindowExTransparent

我有一个正在运行的线程,它不断检查全局鼠标位置与全局控件位置(在其中填充您希望能够单击的控件列表)。全局控件位置是通过获取相对于的控件位置MainWindow,然后添加 的TopLeft属性来确定的MainWindow

当然,这是一个有点“hacky”的解决方案。但如果你找到一个更好的,我会被诅咒的!它似乎对我来说工作正常。(尽管处理奇怪形状的控件可能会变得很奇怪。此代码仅处理矩形控件。)

另外我只是很快把它放在一起,看看它是否会起作用,所以它不是很干净。一个概念证明,如果你愿意的话。


推荐阅读