• 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

     
  • 相关阅读:
    centos下 yum安装ngix
    [转]ORACLE函数大全
    Oracle的DML语言必备基础知识
    微信公众账号开发教程
    freemarker页面如何获取绝对路径basePath
    使用intellij的svn时提示出错: Can't use Subversion command line client: svn.Errors found while svn working copies detection.
    网站地址
    如何让tomcat不记录catalina.out这个日志文件
    在centos6.7用yum安装redis解决办法
    剑指 Offer 06. 从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/weiliuhong/p/wpf-mvvm-loading.html
Copyright © 2020-2023  润新知