前面的文章我们提及过,异步UI的基础实现。基本思路主要是开启新的UI线程,并通过VisualTarget将UI线程上的Visual(即RootVisual)连接到主线程上的UI上即可渲染显示。
但是,之前的实现访问是没有交互能力的,视觉树上的UI并不能实现鼠标事件。那么今天我们就把交互的工作也给完成了。
实现交互能力的核心在于PresentationSource
,它是完成交互功能的核心。它提供了交互源,它是一个抽象类,不能直接使用。我们需要通过继承它来实现交互逻辑。
具体代码如下:
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
来做路由事件的传递。
请看完整的示例:
//异步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代码一样简单使用。