Files
community/InkCanvasForClass/Helpers/PerformanceTransparentWin.cs
T
2025-08-23 21:39:21 +08:00

212 lines
8.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrayNotify;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Shell;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 高性能透明桌面窗口
/// </summary>
public partial class PerformanceTransparentWin : Window
{
static class BrushCreator
{
/// <summary>
/// 尝试从缓存获取或创建颜色笔刷
/// </summary>
/// <param name="color">对应的字符串颜色</param>
/// <returns>已经被 Freeze 的颜色笔刷</returns>
public static SolidColorBrush GetOrCreate(string color)
{
if (!color.StartsWith("#"))
{
throw new ArgumentException($"输入的{nameof(color)}不是有效颜色,需要使用 # 开始");
// 如果不使用 # 开始将会在 ConvertFromString 出现异常
}
if (TryGetBrush(color, out var brushValue))
{
return (SolidColorBrush)brushValue;
}
object convertColor;
try
{
convertColor = ColorConverter.ConvertFromString(color);
}
catch (FormatException)
{
// 因为在 ConvertFromString 会抛出的是 令牌无效 难以知道是为什么传入的不对
throw new ArgumentException($"输入的{nameof(color)}不是有效颜色");
}
if (convertColor == null)
{
throw new ArgumentException($"输入的{nameof(color)}不是有效颜色");
}
var brush = new SolidColorBrush((Color)convertColor);
if (TryFreeze(brush))
{
BrushCacheList.Add(color, new WeakReference<Brush>(brush));
}
return brush;
}
private static Dictionary<string, WeakReference<Brush>> BrushCacheList { get; } =
new Dictionary<string, WeakReference<Brush>>();
private static bool TryGetBrush(string key, out Brush brush)
{
if (BrushCacheList.TryGetValue(key, out var brushValue))
{
if (brushValue.TryGetTarget(out brush))
{
return true;
}
else
{
// 被回收的资源
BrushCacheList.Remove(key);
}
}
brush = null;
return false;
}
private static bool TryFreeze(Freezable freezable)
{
if (freezable.CanFreeze)
{
freezable.Freeze();
return true;
}
return false;
}
}
/// <summary>
/// 创建高性能透明桌面窗口
/// </summary>
public PerformanceTransparentWin()
{
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize;
Stylus.SetIsFlicksEnabled(this, false);
Stylus.SetIsPressAndHoldEnabled(this, false);
Stylus.SetIsTapFeedbackEnabled(this, false);
Stylus.SetIsTouchFeedbackEnabled(this, false);
WindowChrome.SetWindowChrome(this,
new WindowChrome { GlassFrameThickness = WindowChrome.GlassFrameCompleteThickness, CaptionHeight = 0, CornerRadius = new CornerRadius(0), ResizeBorderThickness = new Thickness(0)});
var visualTree = new FrameworkElementFactory(typeof(Border));
visualTree.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(Window.BackgroundProperty));
var childVisualTree = new FrameworkElementFactory(typeof(ContentPresenter));
childVisualTree.SetValue(UIElement.ClipToBoundsProperty, true);
visualTree.AppendChild(childVisualTree);
Template = new ControlTemplate
{
TargetType = typeof(Window),
VisualTree = visualTree,
};
_dwmEnabled = DwmCompositionHelper.DwmIsCompositionEnabled();
if (_dwmEnabled)
{
_hwnd = new WindowInteropHelper(this).EnsureHandle();
Loaded += PerformanceDesktopTransparentWindow_Loaded;
Background = Brushes.Transparent;
}
else
{
AllowsTransparency = true;
Background = BrushCreator.GetOrCreate("#0100000");
_hwnd = new WindowInteropHelper(this).EnsureHandle();
}
}
/// <summary>
/// 设置点击穿透到后面透明的窗口
/// </summary>
public void SetTransparentHitThrough()
{
if (_dwmEnabled)
{
Win32.User32.SetWindowLongPtr(_hwnd, Win32.GetWindowLongFields.GWL_EXSTYLE,
(IntPtr)(int)((long)Win32.User32.GetWindowLongPtr(_hwnd, Win32.GetWindowLongFields.GWL_EXSTYLE) | (long)Win32.ExtendedWindowStyles.WS_EX_TRANSPARENT));
}
else
{
Background = Brushes.Transparent;
}
}
/// <summary>
/// 设置点击命中,不会穿透到后面的窗口
/// </summary>
public void SetTransparentNotHitThrough()
{
if (_dwmEnabled)
{
Win32.User32.SetWindowLongPtr(_hwnd, Win32.GetWindowLongFields.GWL_EXSTYLE,
(IntPtr)(int)((long)Win32.User32.GetWindowLongPtr(_hwnd, Win32.GetWindowLongFields.GWL_EXSTYLE) & ~(long)Win32.ExtendedWindowStyles.WS_EX_TRANSPARENT));
}
else
{
Background = BrushCreator.GetOrCreate("#0100000");
}
}
[StructLayout(LayoutKind.Sequential)]
private struct STYLESTRUCT
{
public int styleOld;
public int styleNew;
}
private void PerformanceDesktopTransparentWindow_Loaded(object sender, RoutedEventArgs e)
{
((HwndSource)PresentationSource.FromVisual(this)).AddHook((IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
{
//想要让窗口透明穿透鼠标和触摸等,需要同时设置 WS_EX_LAYERED 和 WS_EX_TRANSPARENT 样式,
//确保窗口始终有 WS_EX_LAYERED 这个样式,并在开启穿透时设置 WS_EX_TRANSPARENT 样式
//但是WPF窗口在未设置 AllowsTransparency = true 时,会自动去掉 WS_EX_LAYERED 样式(在 HwndTarget 类中)
//如果设置了 AllowsTransparency = true 将使用WPF内置的低性能的透明实现,
//所以这里通过 Hook 的方式,在不使用WPF内置的透明实现的情况下,强行保证这个样式存在。
if (msg == (int)Win32.WM.STYLECHANGING && (long)wParam == (long)Win32.GetWindowLongFields.GWL_EXSTYLE)
{
var styleStruct = (STYLESTRUCT)Marshal.PtrToStructure(lParam, typeof(STYLESTRUCT));
styleStruct.styleNew |= (int)Win32.ExtendedWindowStyles.WS_EX_LAYERED;
Marshal.StructureToPtr(styleStruct, lParam, false);
handled = true;
}
return IntPtr.Zero;
});
}
/// <summary>
/// 是否开启 DWM 了,如果开启了,那么才可以使用高性能的桌面透明窗口
/// </summary>
private readonly bool _dwmEnabled;
private readonly IntPtr _hwnd;
}
}