前言:上一个版本的Winform需要改成WPF来做界面,第一次接触WPF,在转换过程中遇到的需求就是一个背景透明模糊,一个是类似于 加载中…… 这样的等待窗口,等后台执行完毕后再关掉。在Winform中是通过一个类指定等待窗口的parent为调用者,并指定topmost为最顶层来实现。在WPF中这个方法不太灵光,通过这几天的摸索,找到一个WPF下的UI利器--HandyControl(https://github.com/HandyOrg/HandyControl)感谢作者分享。通过它来实现一些界面的效果,它里面带的有个顶部弹出对话框的功能(带遮罩),但这个不支持后台关闭(作者说是可以调用带回调的模式,但没有找到,也没有明确说明是哪个)。所以我就单独从里面把这个功能提取出来,实现了弹出提示框,后台可以关闭的模式。
先看一下HandyControl提供的Demo中的这种对话框。
由于我需要的是弹出后,后台会执行代码,代码执行完后主动关闭对话框的操作。于是我把里面的这块代码单独提取出来改造了一下,实现效果如下。
这是在新接触WPF开发中,学习到的,如何让主窗体背景磨砂透明、如何Grid背景透明模糊、如何让Grid的控件不随Grid来模糊。
下面进入代码:
首先新建一个WPF项目,然后通过Nuget引用HandyControl。
在App.xaml中添加以下内容,来引用HandyControl的样式效果。
<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,用来实现主窗体透明磨砂感。
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 用来实现弹出框
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 用来实现数据绑定给弹出框,比如指定显示文字
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用来实现构建弹出框实例
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 主窗体的内容
<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
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 对话框
<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 用来后台调用关闭它
using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; using WpfApp1.ViewModel; namespace WpfApp1 { /// <summary> /// TextDialog_.xaml 的交互逻辑 /// </summary> public partial class TextDialog { public DialogInfo info = new DialogInfo { MyTxt = "加载中……" }; public TextDialog() { DataContext = info; InitializeComponent(); } public void CloseMe() { var bam = new ButtonAutomationPeer(BtnClose); var iip = bam.GetPattern(PatternInterface.Invoke) as IInvokeProvider; iip?.Invoke(); } } }