WPF MVVM 彈框之等待框


WPF MVVM 彈框之等待框

獨立觀察員 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='是否確認操作?是否確認操作?是否確認操作?是否確認操作?是否確認操作?', TargetNullValue='是否確認操作?'}" TextWrapping="Wrap"
                   VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="{Binding IsShowText, Converter={StaticResource VisibleConverter}, FallbackValue=Visible}">
        </TextBlock>

        <ContentControl Visibility="{Binding IsShowCustom, Converter={StaticResource VisibleConverter}, FallbackValue=Collapsed}" Content="{Binding CustomContent}" 
                        HorizontalAlignment="{Binding CustomContentHorizontalAlignment, TargetNullValue=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

 

 


免責聲明!

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



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