WPF - 如何通过半透明层创建点击
WPF - how to create click through semi transparent layer
我想要一个类似这样的屏幕录制软件。
我的示例 wpf window 看起来像这样
<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
使整个 window 透明,这让我可以点击 window,但是我的 window 的所有事件和其中的控件都不会没用。
任何人都可以建议我如何实现这一目标吗?
无法让 window 的 部分 在视觉上都是半透明的 和 对用户交互透明(例如鼠标点击)。
您要么必须:
- 使整个 window 对用户交互透明(使用
SetWindowLong
、CreateParams
等)
- 或使所需的 window 部分完全透明
解决此问题的方法是手动绘制半透明区域,而无需 window。这将是一项艰巨的工作,据我所知,没有可靠的方法可以做到这一点。 Windows DWM 不为此提供任何 public API,直接在桌面的 HDC 上绘图是行不通的,图形硬件并不总是支持覆盖,Direct2D 也不行让你也这样做。
您可以创建两个最顶层 windows 并同步它们的大小。第一个 window 将只有调整大小的控件和处理鼠标输入,里面没有内容。第二个 window 将显示半透明的灰色背景,内部有一个透明区域 - 就像你当前的示例中的 window - 但对于任何鼠标交互都是完全透明的。
我实际上对此很好奇,看起来确实没有 "proper" 或 "official" 方法可以仅在 window 而不是控件上实现透明度。
代替这个,我想出了一个功能有效的解决方案:
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>
MainWindow 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
将其变成正常的非点击 window。如果鼠标不在控件上,它会使用 SetWindowExTransparent
.
将其切换回点击状态
我有一个线程 运行 不断检查全局鼠标位置与全局控件位置(您在其中填写希望能够单击的控件列表)。全局控制位置是通过获取相对于 MainWindow
的控制位置,然后添加 MainWindow
.
的 Top
和 Left
属性来确定的
当然,这是一个有点 "hacky" 的解决方案。但如果你找到更好的,我会被诅咒!它似乎对我来说工作正常。 (尽管处理奇怪形状的控件可能会变得很奇怪。此代码仅处理矩形控件。)
另外,我只是很快就把它放在一起看它是否可行,所以它不是很干净。一个概念证明,如果你愿意的话。
我想要一个类似这样的屏幕录制软件。
<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
使整个 window 透明,这让我可以点击 window,但是我的 window 的所有事件和其中的控件都不会没用。
任何人都可以建议我如何实现这一目标吗?
无法让 window 的 部分 在视觉上都是半透明的 和 对用户交互透明(例如鼠标点击)。
您要么必须:
- 使整个 window 对用户交互透明(使用
SetWindowLong
、CreateParams
等) - 或使所需的 window 部分完全透明
解决此问题的方法是手动绘制半透明区域,而无需 window。这将是一项艰巨的工作,据我所知,没有可靠的方法可以做到这一点。 Windows DWM 不为此提供任何 public API,直接在桌面的 HDC 上绘图是行不通的,图形硬件并不总是支持覆盖,Direct2D 也不行让你也这样做。
您可以创建两个最顶层 windows 并同步它们的大小。第一个 window 将只有调整大小的控件和处理鼠标输入,里面没有内容。第二个 window 将显示半透明的灰色背景,内部有一个透明区域 - 就像你当前的示例中的 window - 但对于任何鼠标交互都是完全透明的。
我实际上对此很好奇,看起来确实没有 "proper" 或 "official" 方法可以仅在 window 而不是控件上实现透明度。
代替这个,我想出了一个功能有效的解决方案:
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>
MainWindow 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
将其变成正常的非点击 window。如果鼠标不在控件上,它会使用 SetWindowExTransparent
.
我有一个线程 运行 不断检查全局鼠标位置与全局控件位置(您在其中填写希望能够单击的控件列表)。全局控制位置是通过获取相对于 MainWindow
的控制位置,然后添加 MainWindow
.
Top
和 Left
属性来确定的
当然,这是一个有点 "hacky" 的解决方案。但如果你找到更好的,我会被诅咒!它似乎对我来说工作正常。 (尽管处理奇怪形状的控件可能会变得很奇怪。此代码仅处理矩形控件。)
另外,我只是很快就把它放在一起看它是否可行,所以它不是很干净。一个概念证明,如果你愿意的话。