WPF MVVM 彈框之等待框
目錄 隱藏 一、效果 二、彈框主體改造 三、等待影片用戶控制元件 四、彈窗 ViewModel 和幫助類的改造 五、使用方法和代碼地址獨立觀察員 2020年10月13日
之前寫過一篇《WPF MVVM 模式下的彈窗》,里面實作了確認框和訊息框,經過一段時間的演化,目前又新增了可顯示自定義內容的彈框、可進行資訊錄入的彈框、以及本文將要介紹的加載等待框,
一、效果
先來看看效果,首先是其它彈框(動圖):

然后是等待彈框(動圖):

下面來看如何實作,當然,是在之前的基礎上進行的,前一篇文章沒看的話,需要先看一下,或者直接獲取文末提供的代碼查看,
二、彈框主體改造
首先改造的是,給右上角的 X 和底下的確認取消按鈕區域的是否顯示特性 Visibility 系結了相關屬性,可以控制是否顯示,這樣在訊息框情況下可以隱藏底部按鈕,在等待框情況下可以都隱藏掉,

然后是中間的主體區域,圖上看不出什么變化,實際上變化還是比較大的,代碼如下:

文字版:
<ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <StackPanel Margin="5" VerticalAlignment="Center"> <TextBlock FontSize="16" Text="{Binding DialogMessage, FallbackValue='https://www.cnblogs.com/weiliuhong/archive/2020/10/13/是否確認操作?是否確認操作?是否確認操作?是否確認操作?是否確認操作?', TargetNullValue='https://www.cnblogs.com/weiliuhong/archive/2020/10/13/是否確認操作?'}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="{Binding IsShowText, Converter={StaticResource VisibleConverter}, FallbackValue=https://www.cnblogs.com/weiliuhong/archive/2020/10/13/Visible}"> </TextBlock> <ContentControl Visibility="{Binding IsShowCustom, Converter={StaticResource VisibleConverter}, FallbackValue=https://www.cnblogs.com/weiliuhong/archive/2020/10/13/Collapsed}" Content="{Binding CustomContent}" HorizontalAlignment="{Binding CustomContentHorizontalAlignment, TargetNullValue=https://www.cnblogs.com/weiliuhong/archive/2020/10/13/Center, Mode=OneWay}" HorizontalContentAlignment="Center" MinWidth="50"> </ContentControl> </StackPanel> </ScrollViewer>
最外層使用 ScrollViewer 包裹,如果內容過多則可滾動,往里一層是 StackPanel,里面有一個 TextBlock 用于顯示文本內容,還有一個 ContentControl 用于顯示自定義內容(系結一個 FrameworkElement 型別的物件),兩種內容可以分別控制顯示和隱藏,也可以同時顯示,本文介紹的等待框就是使用了同時顯示,
三、等待影片用戶控制元件
按照設想,等待框的影片部分作為自定義內容放入彈框的 ContentControl 中,所以我們需要新建個用戶控制元件,(此節參考朝夕教育 Jovan 老師在 B 站發布的 WPF 教學視頻的“影片實戰”一節)
將一個 Grid 分為四列,每列中放置一個不同顏色的 Border (以 Grid 包裹)并設定 LayoutTransform 變換型別為 ScaleTransform,并給每個 ScaleTransform 命名:

Border 顯示為圓形并居中的代碼為:
<Grid.Resources> <Style TargetType="Border"> <Setter Property="Width" Value="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth, Converter={StaticResource DivideConverter}, ConverterParameter=2}"></Setter> <Setter Property="Height" Value="{Binding RelativeSource={RelativeSource Self}, Path=Width}"></Setter> <Setter Property="CornerRadius" Value="100"></Setter> <!--<Setter Property="LayoutTransform"> <Setter.Value> <ScaleTransform ScaleX="1.6" ScaleY="1.6"></ScaleTransform> </Setter.Value> </Setter>--> </Style> </Grid.Resources>
也就是設定寬度為包裹它的 Grid 的寬度的一半,即每列寬度的一半,這個平分的操作是通過轉換器 DivideConverter 實作的,具體可下載代碼查看,然后,高度系結寬度,這樣就是正方形了,最后再設定圓角,就成圓形了,注釋的部分是設定 LayoutTransform 變換的,具體的 ScaleTransform 變換有個 ScaleX 和 ScaleY 值,分別設定 X 和 Y 方向上的變換數值(變大為 1.6 倍),由于后面需要對這兩個值設定影片,所以此處不能寫死,注釋掉,
影片直接在后臺設定:
private void UC_Wait_OnLoaded(object sender, RoutedEventArgs e) { RunAnimation(); } private void RunAnimation() { //定義影片; DoubleAnimation da = new DoubleAnimation() { Duration = new Duration(TimeSpan.FromMilliseconds(1000)), To = 1.6, RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true, }; Task.Run(async () => { for (int i = 0; i < 4; i++) { Dispatcher.Invoke(() => { var st = FindName($"ST{i + 1}") as ScaleTransform; st?.BeginAnimation(ScaleTransform.ScaleXProperty, da); st?.BeginAnimation(ScaleTransform.ScaleYProperty, da); }); await Task.Delay(300); } }); }
界面載入后執行影片方法,影片方法中先定義了一個 DoubleAnimation 型別的影片:間隔一秒,目標值為 1.6,一直重復,自動反轉,然后在回圈中按照命名規則,依次先使用 FindName 方法找到 ScaleTransform 元素物件,并對其設定 X 和 Y 方向上的影片,等待 300 毫秒再設定下一個,總共四個,
四、彈窗 ViewModel 和幫助類的改造
彈窗 ViewModel 中添加了一個標識是否是等待框的屬性 IsWaitDialog,在倒計時計時器里面,當是等待框時改為正計時,自然也就不會觸發關閉操作,代碼如下:
/// <summary> /// 是否是等待框 /// </summary> public bool IsWaitDialog { get; set; } = false; /// <summary> /// 倒計時計時器 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Timer_Elapsed(object sender, ElapsedEventArgs e) { if (IsWaitDialog) { LeftTime++; } else { LeftTime--; if (LeftTime <= 0) { _timer.Stop(); CloseCommand.Execute(null); } } }
在控制彈框顯示隱藏的屬性 IsShowDialog 的 set 方法中,當是等待框時,倒計時設為零,方便后面(上面說的)直接進行正計時:

關鍵是幫助方法中,新增一個彈出等待框方法:
/// <summary> /// 彈出等待框 /// </summary> /// <param name="vm">相關ViewModel</param> /// <param name="message">訊息內容</param> /// <param name="action">業務方法</param> /// <param name="title">彈窗標題</param> /// <returns></returns> public static async Task ShowWait(ConfirmBoxViewModel vm, string message, Func<Task> action = null, string title = "請耐心等待") { vm.CustomContent = new UC_Wait(); await Task.Run(async () => { vm.IsMessageDialog = false; vm.IsWaitDialog = true; vm.IsShowDialog = true; vm.IsShowText = true; vm.IsShowCustom = true; vm.IsShowButton = false; vm.CustomContentHorizontalAlignment = HorizontalAlignment.Stretch.ToString(); if (!string.IsNullOrWhiteSpace(message)) { vm.DialogMessage = message; } if (!string.IsNullOrWhiteSpace(title)) { vm.DialogTitle = title; } Console.WriteLine($"等待框就緒,業務操作開始執行..."); await Task.Run(async () => { await action?.Invoke(); }).ContinueWith(_ => { vm.IsShowDialog = false; Console.WriteLine($"業務操作執行完畢,等待框關閉."); }); }); }
先將自定義內容設定為等待影片用戶控制元件,接下來是一些顯示方面的設定,
關鍵是如何在執行完業務方法后才關閉彈窗呢?
一開始 Func<Task> action 這個引數我用的還是 Action action,這樣的話,action?.Invoke() 這里不能 await,然后 .NET Core 3.1 又不支持 action?.BeginInvoke(callback, null) 這種寫法,
后來把引數型別改為 Func<Task> ,就可以 await action?.Invoke() 了,而且神奇的是,呼叫的地方不用修改(后面展示),這樣的話,就可以通過如下方式(ContinueWith)達到業務方法執行完成之后關閉彈窗了:
Console.WriteLine($"等待框就緒,業務操作開始執行..."); await Task.Run(async () => { await action?.Invoke(); }).ContinueWith(_ => { vm.IsShowDialog = false; Console.WriteLine($"業務操作執行完畢,等待框關閉."); });
五、使用方法和代碼地址
使用就比較簡單了:
WaitCommand ??= new RelayCommand(o => true, async o => { await ConfirmBoxHelper.ShowWait(DialogVm, "正在執行業務操作...", async () => { await Task.Delay(1000 * 10); Console.WriteLine("操作完成"); }); });
代碼地址:https://gitee.com/dlgcy/WPFTemplate

同步首發:http://dlgcy.com/wpf-mvvm-loading/ 微信公眾號
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/173449.html
標籤:.NET技术
下一篇:再學一次C#(基本型別篇)
