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的樣式效果。

    <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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM