如何绑定WPF托管的UWP InkCanvas中的InkStrokes?
WPF托管UWP InkCanvas并与Elmish.WPF绑定InkStrokes解决方案
问题描述
我是UWP/WinRT新手,目前在WPF窗口中托管自定义WinRT InkCanvas,希望通过WPF窗口的XAML绑定将InkStrokes数据与后端F#模块(Elmish.wpf)关联,但不知如何获取/设置InkContainer中的InkStrokes。当前Elmish.wpf与WinRT InkCanvas已在同一应用中正常运行,以下是已实现的代码:
已实现代码
1. WPF容器——ProgressNoteWindow
<Window x:Class="Stargate.XI.Client.Views.ProgressNoteWindow" 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:xaml="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost" xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls" xmlns:local="clr-namespace:Stargate.XI.Client.Views" WindowStartupLocation="CenterScreen" WindowState="Maximized" mc:Ignorable="d" Title="ProgressNoteWindow" d:DesignHeight="1200" d:DesignWidth="1200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <xaml:WindowsXamlHost x:Name="MyInkControl" Grid.Row="1" InitialTypeName="Loader2.MyUserControl2" ChildChanged="WindowsXamlHost_ChildChanged" /> <Button Command="{Binding SaveCommand}" Content="Save"/> </Grid> </Window>
2. 附加属性——InkCanvasBinder
public static class InkCanvasBinder { public static InkStrokeContainer GetInkStrokes(DependencyObject obj) => obj.GetValue(InkStrokesProperty) as InkStrokeContainer; public static void SetInkStrokes(DependencyObject obj, InkStrokeContainer value) => obj.SetValue(InkStrokesProperty, value); public static DependencyProperty InkStrokesProperty = DependencyProperty.RegisterAttached( "InkStrokes", typeof(InkStrokeContainer), typeof(InkCanvasBinder), new PropertyMetadata(null, InkStrokesProperty_PropertyChanged)); private static void InkStrokesProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var inkCanvas = d as InkCanvas; if (inkCanvas != null) inkCanvas.InkPresenter.StrokeContainer = e.NewValue as InkStrokeContainer; } }
3. UWP项目中的UserControl
<UserControl x:Class="Loader2.MyUserControl2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Loader2" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="1200" d:DesignWidth="1200"> <Grid Background="LightCoral"> <InkCanvas x:Name="TheInkCanvas" Loaded="InkCanvas_OnLoaded" local:InkCanvasBinder.InkStrokes = "{x:Bind MyInkStrokeContainer}"/> <!-- Ensure the InkToolbar is declared after the InkCanvas. If not, the InkCanvas overlay renders the InkToolbar inaccessible., Mode=OneWay--> <InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind TheInkCanvas}" Width="300" Height="50" Margin="10,10,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" /> </Grid> </UserControl>
4. UWP项目中的UserControl代码后置
using Windows.UI.Core; using Windows.UI.Input.Inking; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Loader2 { public sealed partial class MyUserControl2 : UserControl { public MyUserControl2() { this.InitializeComponent(); } private void InkCanvas_OnLoaded(object sender, RoutedEventArgs e) { TheInkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch; } public InkStrokeContainer MyInkStrokeContainer { get { return (InkStrokeContainer)GetValue(MyInkStrokeContainerProperty); } set { SetValue(MyInkStrokeContainerProperty, value); } } public static readonly DependencyProperty MyInkStrokeContainerProperty = DependencyProperty.Register("MyInkStrokeContainer", typeof(InkStrokeContainer), typeof(MyUserControl2), new PropertyMetadata(null)); } }
解决方案步骤
1. 获取托管的UWP控件实例
在ProgressNoteWindow的代码后置中,通过ChildChanged事件保存MyUserControl2的引用,方便后续访问:
private Loader2.MyUserControl2 _inkUserControl; private void WindowsXamlHost_ChildChanged(object sender, EventArgs e) { _inkUserControl = MyInkControl.Child as Loader2.MyUserControl2; }
2. 在Elmish.WPF中同步数据
Elmish.WPF通过Model和Msg管理状态,你需要:
- 在Model中添加存储InkStrokeContainer(或序列化后的字节流)的字段
- 创建对应的Msg(比如
SetInkStrokes InkStrokeContainer、SaveInkStrokes) - 在Update函数中处理这些Msg,更新Model状态
3. 获取InkStrokes用于保存
在Save命令的处理逻辑中,直接通过保存的控件引用获取当前笔触容器:
// 示例Elmish命令处理 let saveInkStrokesCmd (model: Model) = match model.InkUserControlRef with // 假设Model中存储了_inkUserControl的引用 | Some uc -> async { let container = uc.MyInkStrokeContainer use stream = new MemoryStream() do! container.SaveAsync(stream.AsRandomAccessStream()) |> Async.AwaitTask stream.Position <- 0 let bytes = stream.ToArray() // 调用后端API保存字节数据 do! saveStrokesToBackend bytes return SavedSuccessfully } |> Cmd.ofAsyncMsg | None -> Cmd.none
4. 加载InkStrokes到画布
当需要从后端加载数据时,创建InkStrokeContainer并加载数据,然后赋值给UserControl的属性:
// 示例加载逻辑 let loadInkStrokesCmd () = async { let! bytes = getStrokesFromBackend() use stream = new MemoryStream(bytes) let container = new InkStrokeContainer() do! container.LoadAsync(stream.AsRandomAccessStream()) |> Async.AwaitTask return SetInkStrokes container } |> Cmd.ofAsyncMsg // 在Update中处理SetInkStrokes Msg let update msg model = match msg with | SetInkStrokes container -> match model.InkUserControlRef with | Some uc -> uc.MyInkStrokeContainer <- container | None -> () { model with CurrentInkContainer = container }, Cmd.none | _ -> model, Cmd.none
5. 优化附加属性实现双向同步
当前的InkCanvasBinder只支持从属性到画布的单向同步,需要添加笔触变化的监听,反向更新绑定属性:
private static void InkStrokesProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var inkCanvas = d as InkCanvas; if (inkCanvas == null) return; // 移除旧容器的事件监听 if (e.OldValue != null) { inkCanvas.InkPresenter.StrokesCollected -= OnStrokesChanged; inkCanvas.InkPresenter.StrokesErased -= OnStrokesChanged; } inkCanvas.InkPresenter.StrokeContainer = e.NewValue as InkStrokeContainer; // 添加新容器的事件监听 if (e.NewValue != null) { inkCanvas.InkPresenter.StrokesCollected += OnStrokesChanged; inkCanvas.InkPresenter.StrokesErased += OnStrokesChanged; } } private static void OnStrokesChanged(InkPresenter sender, object args) { var inkCanvas = sender.Target as InkCanvas; if (inkCanvas != null) { // 通知属性变化,触发x:Bind更新UserControl的MyInkStrokeContainer SetInkStrokes(inkCanvas, sender.StrokeContainer); } }
内容的提问来源于stack exchange,提问作者Alan Wayne
相关产品推荐
相关产品推荐

