相关文章推荐
追风的作业本  ·  使用層疊樣式表(CSS)設計應用程式的樣式 ...·  2 月前    · 
气宇轩昂的春卷  ·  WPF ListView关闭选择开发者社区·  1 月前    · 
光明磊落的豆腐  ·  “顺德美食”有牌面!最近一年平均每月888个 ...·  4 月前    · 
刚毅的青蛙  ·  达赖喇嘛_百度百科·  1 年前    · 
果断的面包  ·  竞争白热化 ...·  1 年前    · 
礼貌的针织衫  ·  Java8函数之旅 (八) - ...·  1 年前    · 
热心的移动电源  ·  22秒从天堂到地狱 苏霍伊的撞山厄运 ...·  2 年前    · 
Code  ›  wpf DoEvents 用法原理存在的坑推荐方法开发者社区
wpf background
https://cloud.tencent.com/developer/article/1342244
小眼睛的紫菜
2 年前
林德熙
0 篇文章

wpf DoEvents 用法原理存在的坑推荐方法

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
最新优惠活动
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
林德熙
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
社区首页 > 专栏 > 林德熙的博客 > wpf DoEvents 用法原理存在的坑推荐方法

wpf DoEvents 用法原理存在的坑推荐方法

作者头像
林德熙
发布 于 2018-09-18 19:40:53
2.3K 3
发布 于 2018-09-18 19:40:53
举报

如果在执行一段卡UI的代码,这时如何让UI响应。如果存在代码需要获得依赖属性,那么代码就需要在UI线程执行,但是这时就会卡UI,为了让UI响应,所以就需要使用 DoEvents 来让UI响应。 首先需要知道, DoEvents 是在 WinForm 有的,在 WPF 没有这个函数,但是可以自己写出来。

先做一个例子让大家知道 DoEvents 的作用,使用的呆磨很简单,请看代码

<Window x:Class="ZuindmMbx.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ZuindmMbx"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
        <ListView ItemsSource="{Binding KatudefZubpobryk}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"></TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Content="确定" HorizontalAlignment="Left" Margin="424,292,0,0" VerticalAlignment="Top" Width="75" Click="Button_OnClick"/>
    </Grid>
</Window>
   public partial class MainWindow : Window
        public MainWindow()
            InitializeComponent();
            DataContext = this;
        public ObservableCollection<string> KatudefZubpobryk { get; set; } = new ObservableCollection<string>();
        private void Button_OnClick(object sender, RoutedEventArgs e)
            for (int i = 0; i < 10; i++)
                Foo(10);
                KatudefZubpobryk.Add(i.ToString());
        private void Foo(int n)
            for (int i = 0; i < n; i++)
                Foo(n - 1);
    }

这时点击确定可以看到,需要等待一些时间才可以响应界面

如果加上了 DoEvents 就可以看到下图的效果

用法

在呆磨的程序做一些修改,请看代码

        private void Button_OnClick(object sender, RoutedEventArgs e)
            for (int i = 0; i < 10; i++)
                Foo(10);
                KatudefZubpobryk.Add(i.ToString());
                DoEvents();
        public static void DoEvents()
            DispatcherFrame frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
            Dispatcher.PushFrame(frame);
        private static Object ExitFrame(Object state)
            ((DispatcherFrame) state).Continue = false;
            return null;
        }

所以只需要在循环加上代码就可以了。可以复制下面的两个方法到需要使用让UI响应的地方,在需要的地方调用,使用的方法很简单。

建议在下面的地方使用:

  • 后台操作比较耗时,未完全加载也能正常使用
  • 性能已经没有办法优化
  • 性能没有时间优化,可作为临时性方案
  • DoEvents建议一定是在主线程上使用

原理

请看一下底层的 PushFrameImpl 下面的代码有删减

会导致UI重绘的消息:0xC25A及0xC262 所以发送这个消息就可以让UI响应

存在的坑

这里的坑是 PushFrame 的坑,关于他的原理,请看 https://walterlv.github.io/post/dotnet/2017/09/26/dispatcher-push-frame.html

如果点击确定按钮之后,再次点击确定按钮,那么就会出现很多个重复的数。如果使用这个方法,那么需要禁用确定按钮,小心用户多次点击。

在使用方法的时候拖动窗口,可能让窗口卡死。

复现步骤:

修改上面呆磨代码,加上 OnLoaded ,里面使用 Dispatcher.Invoke 或 DoEvents ,然后运行拖动窗口,这时窗口卡死

        public MainWindow()
            InitializeComponent();
            DataContext = this;
            Loaded += OnLoaded;
        private async void OnLoaded(object sender, RoutedEventArgs e)
            await Task.Delay(2000);
            Dispatcher.Invoke(() => { }, DispatcherPriority.Background);
        }

但是这时使用 Alt+Tab 到其他窗口,然后回来,可以看到窗口正常

实际上尝试改变窗口大小也会让窗口卡死,请看 WPF application intermittently hangs when using Dispatcher.Invoke and/or Dispatcher.PushFrame while user is resizing or draging window

OnLoad 上其他坑

我必须说,不仅是 OnLoad 会出现这些坑,在很多情况也会,但是我还不知道条件。

请把 await Task.Delay(2000) 换为 Foo(10); 进行一些计算,这时在软件启动的时候,尝试拖动窗口,可以看到窗口是没有显示内容,但是鼠标放开的时候,就可以看到界面显示。

        private void OnLoaded(object sender, RoutedEventArgs e)
            Foo(10);
            Dispatcher.Invoke(() =>
            }, DispatcherPriority.Background);
        }

接着把 Invoke 换为 DoEvents ,结果相同,在启动拖动窗口,窗口没有内容。

使用 DispatcherTimer 出现窗口冻结

下面的代码是创建一个 time 不停在里面使用 Dispatcher.Invoke

        public MainWindow()
            InitializeComponent();
            DataContext = this;
            Loaded += OnLoaded;
            DispatcherTimer time = new DispatcherTimer();
            time.Interval = new TimeSpan(0, 0, 1);
            time.Tick += Time_Tick;
            time.Start();
        private void Time_Tick(object sender, EventArgs e)
            Foo(10);
            Dispatcher.Invoke(() => { }, DispatcherPriority.Background);
        }

这时拖动窗口会出现冻结,和上面一样。

实际把上面代码的运算去掉也会冻住,但是我尝试10次,有2次在放开的时候才冻住。

推荐方法

实际上垃圾wr是不是要让开发者去写这样的方法?实际上垃圾wr已经做了这个东西,但是没有直接告诉开发者,请尝试使用下面的代码代替上面呆磨

        private void Button_OnClick(object sender, RoutedEventArgs e)
            for (int i = 0; i < 10; i++)
                Foo(10);
                KatudefZubpobryk.Add(i.ToString());
                Dispatcher.Invoke(() => { }, DispatcherPriority.Background);
        }

关键就是 Dispatcher.Invoke(() => { }, DispatcherPriority.Background); ,这句代码就是在主线程插入一个 Background 因为优先级,所以这时就可以让UI处理其他的输入

但是直接使用 Dispatcher.Invoke 代码太长,是不是可以使用比较简单的?实际上还是有的,请看代码。

        private async void Button_OnClick(object sender, RoutedEventArgs e)
            for (int i = 0; i < 10; i++)
                Foo(10);
                KatudefZubpobryk.Add(i.ToString());
                await System.Windows.Threading.Dispatcher.Yield();
        }

实际上 System.Windows.Threading.Dispatcher.Yield 这个方法的实现和 Dispatcher.Invoke(() => { }, DispatcherPriority.Background 一点也不同,他使用的是 async 以及其他我还不知道怎么说的科技。

最后的方法是在UI主线程执行的函数上添加 async 和直接使用 Dispatcher.Yield 就可以在循环中让UI响应。不会在循环中让UI卡住。

建议使用最后的方法,因为这个方法可以解决坑,而且使用简单

实际上,使用了上面无论哪个方法都不会让界面一直都响应,如果页面有一个循环的动画,就可以看到动画播放实际上有些卡,下面写一个呆磨就可以知道。在上面的界面添加下面的代码,不停做动画。

        <Grid>
            <Grid.Triggers>
                <EventTrigger RoutedEvent="Grid.Loaded">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever">
                            <DoubleAnimation Storyboard.TargetName="T" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:1"></DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Grid.Triggers>
            <Grid x:Name="G" Background="#565656" Width="200" Height="200" 
 
推荐文章
追风的作业本  ·  使用層疊樣式表(CSS)設計應用程式的樣式 - .NET MAUI | Microsoft Learn
2 月前
气宇轩昂的春卷  ·  WPF ListView关闭选择开发者社区
1 月前
光明磊落的豆腐  ·  “顺德美食”有牌面!最近一年平均每月888个老板入行_南方plus_南方+
4 月前
刚毅的青蛙  ·  达赖喇嘛_百度百科
1 年前
果断的面包  ·  竞争白热化 乳腺癌用药集采价降至“地板”?_手机新浪网
1 年前
礼貌的针织衫  ·  Java8函数之旅 (八) - 组合式异步编程 - 祈求者- - 博客园
1 年前
热心的移动电源  ·  22秒从天堂到地狱 苏霍伊的撞山厄运 超级喷气100型客机印尼试飞空难
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号