WPF 手把手教你写跨线程UI控件

多线程UI,是winform里面是一件非常简单的事情,然而在WPF里面,想要做到跨线程的UI渲染,可就没那么简单了。

我们知道,在Winform中,我们只需要在多线程里直接new一个新的窗口就可以实现多线程UI了。这对我们解决UI卡顿的问题有一定的帮助。

今天我们重点来学习一下,在WPF程序中,怎么去实现一个跨线程UI控件。

基本思路

在WPF中,负责线程调度的管理器是Dispatcher,UI线程必须要在主线程中执行,而且主线程必须是单线程实例。

UI线程实际上是运行在一个消息循环机制中,有DispatcherFrame以及消息处理,转发等机制。

所以我们要实现多线程UI,首先需要有一个专门的UI消息处理线程;其次需要将多线程上的UI渲染合并到现有主线程上去,以便可以加载到其他的主线程UI窗体上。

具体实现方法

定义UI线程,直接看代码

/// <summary>
    /// 管理 DynamicRenderer 线程
    /// </summary>
    public class DispatcherUIThread : IDisposable
    {
        /// <summary>
        /// 启动新的线程
        /// </summary>
        public void Start()
        {
            _syncThreadStartResetEvent = new AutoResetEvent(false);
            var dynamicRendererThread = new Thread(InternalRunning)
            {
                Priority = ThreadPriority.Highest,
                IsBackground = true,
            };
            //设置线程为单线程
            dynamicRendererThread.SetApartmentState(ApartmentState.STA);
            dynamicRendererThread.Start();
            //等待Dispatcher已启动
            _syncThreadStartResetEvent.WaitOne();
            _syncThreadStartResetEvent.Dispose();
            _syncThreadStartResetEvent = null;
        }

        private AutoResetEvent _syncThreadStartResetEvent;

        /// <summary>
        /// 运行笔的线程
        /// </summary>
        public Dispatcher Dispatcher { get; private set; }

        [SecurityCritical]
        private void InternalRunning()
        {
            Dispatcher = Dispatcher.CurrentDispatcher;
            _syncThreadStartResetEvent.Set();
            //开启消息循环
            Dispatcher.Run();
        }

        /// <inheritdoc />
        public void Dispose()
        {
            Close();
            Dispatcher = null;
        }

        /// <summary>
        /// 关闭线程
        /// </summary>
        public void Close()
        {
            Dispatcher.InvokeShutdown();
        }
    }

跨线程UI合并

跨线程UI合并需要用到俩个关键类:HostVisualVisualTarget

HostVisual:用于承载多线程UI的子元素宿主,该Visual处于主线程上。 

VisualTarget:用于衔接主线程和多线程UI的边界处理器。代码如下:

//异步UI容器
    public class AsyncUIContainer : UIElement
    {
        public AsyncUIContainer()
        {
            Initialize();
        }

        HostVisual hostVisual;
        ContainerVisual Container;
        Rect _hitTestRect = Rect.Empty;

        /// <summary>
        /// 
        /// </summary>
        void Initialize()
        {
            //定义主线程UI宿主
            hostVisual = new HostVisual();
            //开启UI线程
            var thread = new DispatcherUIThread();
            thread.Start();

            thread.Dispatcher.Invoke(() =>
            {
                //在新的UI线程中,初始化Visual容器
                Container = new ContainerVisual();
                //先此线程的UI和主线程UI衔接
                new VisualTarget(hostVisual)
                {
                    RootVisual = Container
                };
            });
            //将UI宿主,添加到视觉树
            AddVisualChild(hostVisual);
        }

        //因为是多线程上的UI,所以需要使用该UI所在线程才可调度
        public Dispatcher UIDispatcher
        {
            get
            {
                return Container.Dispatcher;
            }
        }

        public VisualCollection Children
        {
            get
            {
                return Container.Children;
            }
        }

        //重写命中测试,让多线程UI能够响应基础输入
        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            if (_hitTestRect.Contains(hitTestParameters.HitPoint))
            {
                return new PointHitTestResult(this, hitTestParameters.HitPoint);
            }

            return base.HitTestCore(hitTestParameters);
        }

        //重写布局系统,让多线程UI元素能正常布局
        protected override Size MeasureCore(Size availableSize)
        {
            if (availableSize.Height > 0 && availableSize.Width > 0 && !(double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width)))
            {
                return availableSize;
            }

            return new Size(200, 200);
        }

        //重写布局系统,让多线程UI元素能正常布局
        protected override void ArrangeCore(Rect finalRect)
        {
            base.ArrangeCore(finalRect);

            RenderSize = finalRect.Size;
            _hitTestRect = finalRect;
        }

        //指定起Visual的数量
        protected override int VisualChildrenCount => 1;
        //指定需要渲染的Visual对象
        protected override Visual GetVisualChild(int index)
        {
            return hostVisual;
        }
    }

基础实现方法就差不多了,大家可以根据自己需要进行魔改。

如何使用

多线程的UI控件我们已经写好了,我们只需要将AsyncUIContainer添加到我们的Xaml中。

对于其子元素,我们需要在后台代码中添加(想要在Xaml中添加也可以,需要自己实现Xaml扩展代码,让其支持直接在xaml中编辑)。

            asyncUIContainer.UIDispatcher.InvokeAsync(() =>
            {
                var Rectangle = new Rectangle()
                 {
                    Fill=Brushes.Red,
                    Height=400,
                    Width=400,
                 };

                //这里必须调用Arrange方法,否则界面不会渲染。
                //如果使用DrawingVisual,那么需要调用DrawingVisual的RenderOpen()方法,并主动调用渲染的绘制原语才可正常渲染。
                //下面是个DrawingVisual的例子
                //public void Draw()
                //{
                //    using var drawingContext = RenderOpen();
                //    drawingContext.DrawGeometry(_fillBrush, null, DrawingGeometry);
                //}

                rectangle.Arrange(new Rect(0, 0, 400, 400));
                asyncUIContainer.Children.Add(rectangle);
            });

通过以上就可以显示我们的多线程UI了。

在实际业务中,如果有遇到卡主线程UI的场景,便有用武之地了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷眼Σ(-᷅_-᷄๑)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值