WPF实现背景透明磨砂,并通过HandyControl组件实现弹出等待框
前言:上一个版本的Winform需要改成WPF来做界面,第一次接触WPF,在转换过程中遇到的需求就是一个背景透明模糊,一个是类似于 加载中…… 这样的等待窗口,等后台执行完毕后再关掉。在Winform中是通过一个类指定等待窗口的parent为调用者,并指定topmost为最顶层来实现。在WPF中这个方法不太灵光,通过这几天的摸索,找到一个WPF下的UI利器--HandyControl(https://github.com/HandyOrg/HandyControl)感谢作者分享。通过它来实现一些界面的效果,它里面带的有个顶部弹出对话框的功能(带遮罩),但没找到后台代码关闭的方法。所以我就单独从里面把这个功能提取出来,实现了弹出提示框,后台可以关闭的模式。
最新更新:控件作者提供了有一个自动关闭的Demo,请以Demo的使用方法为准。下载地址:点击下载
先看一下HandyControl提供的Demo中的这种对话框。
由于我需要的是弹出后,后台会执行代码,代码执行完后主动关闭对话框的操作。于是我把里面的这块代码单独提取出来改造了一下,实现效果如下。
这是在新接触WPF开发中,学习到的,如何让主窗体背景磨砂透明、如何Grid背景透明模糊、如何让Grid的控件不随Grid来模糊。
下面进入代码:
首先新建一个WPF项目,然后通过Nuget引用HandyControl。
在App.xaml中添加以下内容,来引用HandyControl的样式效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source= "pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" /> <ResourceDictionary Source= "pack://application:,,,/HandyControl;component/Themes/Theme.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> <ResourceDictionary> <viewModel:ViewModelLocator x:Key= "Locator" /> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> |
添加一个类文件BlurBehind.cs,用来实现主窗体透明磨砂感。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | using System; using System.Runtime.InteropServices; namespace WpfApp1 { /// <summary> /// 背景磨砂 /// </summary> public class BlurBehind { internal enum AccentState { ACCENT_DISABLED = 1, ACCENT_ENABLE_GRADIENT = 0, ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, ACCENT_ENABLE_BLURBEHIND = 3, ACCENT_INVALID_STATE = 4, ACCENT_ENABLE_ACRYLICBLURBEHIND = 5 } [StructLayout(LayoutKind.Sequential)] internal struct AccentPolicy { public AccentState AccentState; public int AccentFlags; public int GradientColor; public int AnimationId; } [StructLayout(LayoutKind.Sequential)] internal struct WindowCompositionAttributeData { public WindowCompositionAttribute Attribute; public IntPtr Data; public int SizeOfData; } internal enum WindowCompositionAttribute { // ... WCA_ACCENT_POLICY = 19 // ... } } } |
然后新建两个目录:ViewModel和Images
在Images中放入一张图片,并设置生成时自动复制
在ViewModel中新建三个类文件
DialogDemoViewModel.cs 用来实现弹出框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using System; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using HandyControl.Controls; namespace WpfApp1.ViewModel { public class DialogDemoViewModel : ViewModelBase { private string _dialogResult; public string DialogResult { get => _dialogResult; #if netle40 set => Set(nameof(DialogResult), ref _dialogResult, value); #else set => Set( ref _dialogResult, value); #endif } public RelayCommand<TextDialog> ShowTextCmd => new Lazy<RelayCommand<TextDialog>>(() => new RelayCommand<TextDialog>(ShowText)).Value; private static void ShowText(TextDialog d) { Dialog.Show(d); //获得句柄 //var dialogShow = Dialog.Show(d); //var dialogShowHwnd = (HwndSource)PresentationSource.FromVisual(dialogShow); //if (dialogShowHwnd == null) return; //var hwnd = dialogShowHwnd.Handle; } } } |
DialogInfo.cs 用来实现数据绑定给弹出框,比如指定显示文字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; using System.ComponentModel; namespace WpfApp1.ViewModel { public class DialogInfo : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public DialogInfo() { MyTxt = "加载中,请稍后。" ; } private string myTxt; public string MyTxt { get => myTxt; set { myTxt = value; PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( "MyTxt" )); } } } } |
ViewModelLocator.cs用来实现构建弹出框实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System; using System.Windows; using CommonServiceLocator; using GalaSoft.MvvmLight.Ioc; namespace WpfApp1.ViewModel { public class ViewModelLocator { public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register<DialogDemoViewModel>(); } public static ViewModelLocator Instance => new Lazy<ViewModelLocator>(() => Application.Current.TryFindResource( "Locator" ) as ViewModelLocator).Value; #region Vm public DialogDemoViewModel DialogDemo => ServiceLocator.Current.GetInstance<DialogDemoViewModel>(); #endregion } } |
MainWindow.xaml 主窗体的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <Window xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d= "http://schemas.microsoft.com/expression/blend/2008" xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc= "http://schemas.openxmlformats.org/markup-compatibility/2006" x:Class= "WpfApp1.MainWindow" mc:Ignorable= "d" Title= "MainWindow" Height= "450" Width= "800" DataContext= "{Binding DialogDemo,Source={StaticResource Locator}}" Loaded= "MainWindow_OnLoaded" Background= "#727A7A7A" AllowsTransparency= "True" WindowStyle= "None" MouseDown= "MainWindow_OnMouseDown" > <Grid HorizontalAlignment= "Left" Height= "397" Margin= "10,10,0,0" VerticalAlignment= "Top" Width= "790" ZIndex= "0" > <Grid Margin= "0,10,10,97" > <Grid.Background> <ImageBrush ImageSource= "/WpfApp1;component/Images/wow_cataclysm_artwork-wallpaper-960x540.jpg" ></ImageBrush> </Grid.Background> <Grid.Effect> <BlurEffect Radius= "8" ></BlurEffect> </Grid.Effect> </Grid> <Button x:Name= "Btn_Show" Content= "Button" HorizontalAlignment= "Left" Margin= "430,185,0,0" VerticalAlignment= "Top" Width= "75" Click= "Button_Click" /> <TextBlock x:Name= "txtBlock" HorizontalAlignment= "Left" Margin= "614,120,0,0" TextWrapping= "Wrap" Text= "" VerticalAlignment= "Top" Height= "120" Width= "145" Foreground= "White" /> </Grid> </Window> |
MainWindow.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | using System; using System.Linq; using System.Runtime.InteropServices; using System.Timers; using System.Windows; using System.Windows.Input; using System.Windows.Interop; using WpfApp1.ViewModel; namespace WpfApp1 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow { [DllImport( "user32.dll" )] private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref BlurBehind.WindowCompositionAttributeData data); private uint _blurOpacity; public double BlurOpacity { get { return _blurOpacity; } set { _blurOpacity = ( uint )value; EnableBlur(); } } private uint _blurBackgroundColor = 0x990000; /* BGR color format */ public MainWindow() { InitializeComponent(); } private void Button_Click( object sender, RoutedEventArgs e) { var newWindow = new TextDialog(); var dialog = new DialogDemoViewModel(); if (dialog.ShowTextCmd.CanExecute(newWindow)) { dialog.ShowTextCmd.Execute(newWindow); } newWindow.info.MyTxt= "加载中" ; //if (DataContext is DialogDemoViewModel MyVM && MyVM.ShowTextCmd.CanExecute(newWindow)) // MyVM.ShowTextCmd.Execute(newWindow); var i = 0; var timer = new Timer(1000); timer.Elapsed+= delegate { Dispatcher.BeginInvoke( new Action(() => { if (i < 5) { txtBlock.Text +=$ "{5 - i}秒后关闭" + Environment.NewLine; i++; } else { newWindow.CloseMe(); } })); }; timer.AutoReset = true ; timer.Enabled = true ; } /// <summary> /// 获取当前应用中处于激活的一个窗口 /// </summary> /// <returns></returns> private static Window GetActiveWindow() => Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive); private void MainWindow_OnLoaded( object sender, RoutedEventArgs e) { EnableBlur(); } private void EnableBlur() { var windowHelper = new WindowInteropHelper( this ); var accent = new BlurBehind.AccentPolicy { AccentState = BlurBehind.AccentState.ACCENT_ENABLE_BLURBEHIND, //GradientColor = (int) ((_blurOpacity << 24) | (_blurBackgroundColor & 0xFFFFFF)) }; var accentStructSize = Marshal.SizeOf(accent); var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false ); var data = new BlurBehind.WindowCompositionAttributeData { Attribute = BlurBehind.WindowCompositionAttribute.WCA_ACCENT_POLICY, SizeOfData = accentStructSize, Data = accentPtr }; SetWindowCompositionAttribute(windowHelper.Handle, ref data); Marshal.FreeHGlobal(accentPtr); } private void MainWindow_OnMouseDown( object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) DragMove(); } } } |
TextDialog.xaml 对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <Border x:Class= "WpfApp1.TextDialog" xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:hc= "https://handyorg.github.io/handycontrol" CornerRadius= "10" Width= "400" Height= "247" Background= "{DynamicResource RegionBrush}" > <hc:SimplePanel> <TextBlock x:Name= "TextBlock" Style= "{StaticResource TextBlockLargeBold}" Text= "{Binding MyTxt,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment= "Left" Margin= "100,0,0,119" VerticalAlignment= "Bottom" Height= "68" Width= "100" /> <Button x:Name= "BtnClose" Width= "22" Height= "22" Command= "hc:ControlCommands.Close" Style= "{StaticResource ButtonIcon}" Foreground= "{DynamicResource PrimaryBrush}" hc:IconElement.Geometry= "{StaticResource ErrorGeometry}" Padding= "0" HorizontalAlignment= "Right" VerticalAlignment= "Top" Margin= "0,4,4,0" Visibility= "Hidden" /> </hc:SimplePanel> </Border> |
TextDialog.xaml.cs 新增了一个CloseMe 用来后台调用关闭它
1 using System.Windows.Automation.Peers; 2 using System.Windows.Automation.Provider; 3 using WpfApp1.ViewModel; 4 5 namespace WpfApp1 6 { 7 /// <summary> 8 /// TextDialog_.xaml 的交互逻辑 9 /// </summary> 10 public partial class TextDialog 11 { 12 public DialogInfo info = new DialogInfo { MyTxt = "加载中……" }; 13 public TextDialog() 14 { 15 DataContext = info; 16 InitializeComponent(); 17 } 18 public void CloseMe() 19 { 20 try 21 { 22 BtnClose.Visibility = Visibility.Visible; 23 BtnClose.OnClick(); 24 } 25 catch 26 { 27 // 28 } 29 30 } 31 } 32 /// <summary> 33 /// ButtonBase 扩展 34 /// </summary> 35 public static class ButtonBaseExtension 36 { 37 /// <summary> 38 /// 引发 <see> 39 /// <cref>Primitives.ButtonBase.Click</cref> 40 /// </see> 41 /// 路由事件。 42 /// </summary> 43 /// <param name="buttonBase">要引发路由事件的按钮</param> 44 public static void OnClick(this System.Windows.Controls.Primitives.ButtonBase buttonBase) 45 { 46 buttonBase.GetType().GetMethod(nameof(OnClick), BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(buttonBase, null); 47 } 48 } 49 50 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)