前言:上一個版本的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的樣式效果。
<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 用來后台調用關閉它
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 }
