WPF异步UI交互功能的实现方法

前面的文章我们提及过,异步UI的基础实现。基本思路主要是开启新的UI线程,并通过VisualTarget将UI线程上的Visual(即RootVisual)连接到主线程上的UI上即可渲染显示。

但是,之前的实现访问是没有交互能力的,视觉树上的UI并不能实现鼠标事件。那么今天我们就把交互的工作也给完成了。

实现交互能力的核心在于PresentationSource,它是完成交互功能的核心。它提供了交互源,它是一个抽象类,不能直接使用。我们需要通过继承它来实现交互逻辑。

具体代码如下:

cs 复制代码
 public class VisualTargetPresentationSource : PresentationSource
        {
            public VisualTargetPresentationSource(HostVisual hostVisual)
            {
                _visualTarget = new VisualTarget(hostVisual);
            }

            public override Visual RootVisual
            {
                get
                {
                    return _visualTarget.RootVisual;
                }

                set
                {
                    Visual oldRoot = _visualTarget.RootVisual;

                    _visualTarget.RootVisual = value;

                    //此处是关键代码,通过根的时间通知,触发Load事件。通知视觉树,元素已经加载。
                    //元素加载后,IsVisible会变成True,命中测试才能开始工作
                    RootChanged(oldRoot, value);
                }
            }

            public override bool IsDisposed
            {
                get
                {
                    return false;
                }
            }

            protected override CompositionTarget GetCompositionTargetCore()
            {
                return _visualTarget;
            }

            private VisualTarget _visualTarget;
        }

有了上述代码,我们的异步UI具备了交互能力。但是此时,我们用鼠标点击元素,依然是不会有动作发生的。

因为我们的UI是在异步线程上,它的功能受到一定的阉割,它并不能直接响应鼠标或者键盘事件。所以我们需要通过VisualHost来做路由事件的传递。

请看完整的示例:

cs 复制代码
//异步UI容器
    public class AsyncUIContainer : UIElement
    {
        public AsyncUIContainer()
        {
            Initialize();
        }

        private HostVisual hostVisual;

        private Grid RootVisual;

        private DispatcherUIThread dispatcherUIThread;

        /// <summary>
        /// 
        /// </summary>
        private void Initialize()
        {
            hostVisual = new HostVisual();

            dispatcherUIThread = new DispatcherUIThread();

            dispatcherUIThread.Start();

            dispatcherUIThread.Dispatcher.Invoke(() =>
            {
                RootVisual = new Grid();

                new VisualTargetPresentationSource(hostVisual)
                {
                    RootVisual = RootVisual
                };
            });

            AddVisualChild(hostVisual);
        }

        public void UpdateRootLayout(Action<Grid> action)
        {
            if (action != null)
            {
                RootVisual.Dispatcher.Invoke(() =>
                {
                    action(RootVisual);
                });
            }
        }

        #region override

        private UIElement _hitElement;

        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            RootVisual.Dispatcher.Invoke(() =>
            {
                _hitElement = (UIElement)RootVisual.InputHitTest(hitTestParameters.HitPoint);
            });

            if (_hitElement != null)
            {
                return new PointHitTestResult(this, hitTestParameters.HitPoint);
            }
            else
            {
                return null;
            }
        }

        //此处是用来做路由事件转发,用于触发UI线程上的元素事件
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            if (_hitElement != null)
            {
                _hitElement.Dispatcher.Invoke(() => _hitElement.RaiseEvent(e));
            }
        }

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

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

            RootVisual.Dispatcher.Invoke(() =>
            {
                RootVisual.Arrange(finalRect);
            });

            RenderSize = finalRect.Size;
        }

        //指定起Visual的数量
        protected override int VisualChildrenCount => 1;

        //指定需要渲染的Visual对象
        protected override Visual GetVisualChild(int index)
        {
            return hostVisual;
        }

        public void Dispose()
        {
            dispatcherUIThread?.Dispose();
        }
        #endregion

        public class VisualTargetPresentationSource : PresentationSource
        {
            public VisualTargetPresentationSource(HostVisual hostVisual)
            {
                _visualTarget = new VisualTarget(hostVisual);
            }

            public override Visual RootVisual
            {
                get
                {
                    return _visualTarget.RootVisual;
                }

                set
                {
                    Visual oldRoot = _visualTarget.RootVisual;

                    _visualTarget.RootVisual = value;

                    RootChanged(oldRoot, value);
                }
            }

            public override bool IsDisposed
            {
                get
                {
                    return false;
                }
            }

            protected override CompositionTarget GetCompositionTargetCore()
            {
                return _visualTarget;
            }

            private VisualTarget _visualTarget;
        }
    }

到这里,你就可以愉快的玩耍了。下期我们再实现异步UI在XAML上的编辑能力,可以在xaml中添加普通的xaml代码一样简单使用。

相关推荐
椒颜皮皮虾྅12 小时前
让文档不止于文字:DeploySharp + PP-Structure,打通版面分析与表格重建
开发语言·学习·c#·.net·cuda
椒颜皮皮虾྅14 小时前
一套 C# 接口,解锁四类视觉任务:DeploySharp 分类、分割、关键点与旋转框实战
开发语言·分类·c#
罗迪尼亚的熔岩15 小时前
联合kuka机器人, 海康visonmaster 的手眼9点标定方法
机器人·c#·工业相机·标定
她说彩礼65万15 小时前
C# TaskCompletionSource
java·开发语言·c#
泛凡(Linyongui)1 天前
基于 CW32L010 的智能厨卫调温旋钮方案--低成本 EC21 编码旋钮全解析
单片机·嵌入式硬件·c#
暴走约伯1 天前
【VFX工具链源码拆解 02】告别机械劳动(上):一键统改百个子粒子颜色与批量重命名
3d·unity·c#·材质
信息安全失业大专人员1 天前
每日了解一个智能体-WorkBuddy
开发语言·ai·c#·智能体
呆呆敲代码的小Y1 天前
【实战精选面试题】C# 面试题详细解答,按难易程度排序
android·java·游戏·c#·游戏开发·unity面试题·c#面试题
淡海水1 天前
09-01-内存-Unity资源生命周期全景
unity·c#·游戏引擎·.netcore·gc
椒颜皮皮虾྅2 天前
让 C# 读懂整张图:DeploySharp 驱动 PaddleOCR v4/v5/v6,打通 OCR 全流程
开发语言·人工智能·深度学习·学习·c#·ocr·cuda