从以编程方式生成的子元素访问放置在父元素中的 InkToolbar

Access InkToolbar placed in parent element from programmatically generated child elements

我有一个 UserControl,其中包含一个 InkToolbar。在此控件中,我根据用户输入生成了另一组 UserControls。在每个以编程方式生成的控件中,都包含一个 InkCanvas.

这是父级 UserControl & 后面的代码。

Main.xaml

<UserControl
    x:Class="uwp.Main"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:uwp_mvvm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <InkToolbar x:Name="InkToolbar" Background="Black" Margin="10 0 0 0" VerticalAlignment="Center"/>
        </StackPanel>

        <ScrollViewer ZoomMode="Enabled" Background="DarkGray" x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MinZoomFactor="0.25" Width="Auto" Height="Auto" MaxZoomFactor="4" Grid.Row="1">
            <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
                <ItemsControl x:Name="PagesItemsControl" ItemsSource="{x:Bind Pages, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Top">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <WrapGrid Orientation="Vertical"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <local:TestControl x:Name="TestControl" Page="{Binding}" Margin="4 4" InkToolbarControl="{Binding Path=InkToolbar, ElementName=InkToolbar}" />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</UserControl>

Main.xaml.cs

public sealed partial class MainViewer : UserControl
{
    public MainViewer()
    {
        this.InitializeComponent();
    }

    private void EventTriggerChanged(object sender, PropertyChangedEventArgs e)
    {
        var trigger = sender as EventTriggerTestControl;
        if (trigger != null)
            RaiseChanged(e.PropertyName);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }

    private PdfDocument pdfDocument = null;
    private string password = string.Empty;
    private ObservableCollection<PdfPage> pages = new ObservableCollection<PdfPage>();

    public string Password
    {
        private get { return password; }
        set { if (value != password) { password = value; } }
    }

    public ObservableCollection<PdfPage> Pages
    {
        get { return pages; }
        set { if (value != pages) { pages = value; } }
    }

    public StorageFile File
    {
        get { return (StorageFile)GetValue(FileProperty); }
        set { SetValue(FileProperty, value); }
    }

    public static readonly DependencyProperty FileProperty =
        DependencyProperty.Register(nameof(File), typeof(StorageFile), typeof(MainViewer), new PropertyMetadata(null, new PropertyChangedCallback(OnDocumentChanged)));

    private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
            return;

        d.RegisterPropertyChangedCallback(FileProperty, CaptureDocument);
    }

    private async static void CaptureDocument(DependencyObject sender, DependencyProperty dp) => await (sender as MainViewer).OpenFileAsync(sender.GetValue(dp) as StorageFile);

    private async Task OpenFileAsync(StorageFile file)
    {
        try
        {
            if (file == null)
                throw new ArgumentNullException(nameof(file));

            var files = await ApplicationData.Current.LocalFolder.GetFilesAsync(CommonFileQuery.OrderByName);

            if (files.Where(x => x.Name == file.Name).ToList().Count == 0)
                await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);

            file = await ApplicationData.Current.LocalFolder.GetFileAsync(file.Name);
            pdfDocument = new PdfDocument(file.Path, Password);

            pdfDocument.Pages.ToList().ForEach(x => Pages.Add(x));
        }
        catch (Exception ex) { throw ex; }
    }
}

我想使用 TestControl

中的父控件中的 InkToolbar

TestControl.xaml

<UserControl
    x:Class="uwp.TestControl"
    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="using:uwp_mvvm.ViewModels" xmlns:converter="using:uwp_mvvm"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <UserControl.Resources>
        <converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
    </UserControl.Resources>

    <Grid x:Name="Viewport" VerticalAlignment="Top" HorizontalAlignment="Center">
        <Border x:Name="ViewportBorder" Background="White" BorderThickness="2, 2, 2, 2" BorderBrush="#FF353334" />
        <Image x:Name="Image" Margin="0" UseLayoutRounding="True" ManipulationMode="Scale"/>
        <Canvas x:Name="SelectionCanvas" CompositeMode="MinBlend" Opacity="1" />
        <InkCanvas x:Name="InkCanvas" />
    </Grid>
</UserControl>

TastControl.xaml.cs

public sealed partial class TestControl : UserControl
{
    private const float DPI = 256;

    public PdfPage Page
    {
        get { return (PdfPage)GetValue(PageProperty); }
        set { SetValue(PageProperty, value); }
    }

    public static readonly DependencyProperty PageProperty =
        DependencyProperty.Register(nameof(Page), typeof(PdfPage), typeof(TestControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPageChanged)));

    private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
            return;

        d.RegisterPropertyChangedCallback(PageProperty, CapturePage);
    }

    private static void CapturePage(DependencyObject sender, DependencyProperty dp) => (sender as TestControl).RenderPage(sender.GetValue(dp) as PdfPage);
    
    public InkToolbar InkToolbarControl
    {
        get { return (InkToolbar)GetValue(InkToolbarControlProperty); }
        set { SetValue(InkToolbarControlProperty, value); }
    }

    public static readonly DependencyProperty InkToolbarControlProperty =
            DependencyProperty.Register(nameof(InkToolbarControl), typeof(InkToolbar), typeof(TestControl), new PropertyMetadata(null));

    public TestControl()
    {
        this.InitializeComponent();

        ConfigureInkCanvas();
    }

    private void ConfigureInkCanvas()
    {
        InkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
        InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
        InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(
            new InkDrawingAttributes
            {
                IgnorePressure = false,
                FitToCurve = true,
                Color = Colors.Black,
                PenTip = PenTipShape.Circle
            });

        InkCanvas.InkPresenter.UnprocessedInput.PointerEntered -= InkCanvasUnprocessedInputPointerEntered;
        InkCanvas.InkPresenter.UnprocessedInput.PointerEntered += InkCanvasUnprocessedInputPointerEntered;

        InkCanvas.InkPresenter.UnprocessedInput.PointerExited -= InkCanvasUnprocessedInputPointerExited;
        InkCanvas.InkPresenter.UnprocessedInput.PointerExited += InkCanvasUnprocessedInputPointerExited;
    }

    private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
    {
        //InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
        //InkToolbarControl.TargetInkCanvas = InkCanvas;
    }

    private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
    {
        //InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
        //InkToolbarControl.TargetInkCanvas = null;
    }

    private void RenderPage(PdfPage page, float dpi = DPI)
    {
        if (page is null)
            return;

        this.DataContext = page;
        var width = (DataContext as PdfPage).Width;
        var height = (DataContext as PdfPage).Height;

        var writableBitmap = new WriteableBitmap((int)(width / 72f * dpi), (int)(height / 72f * dpi));
        (DataContext as PdfPage).Render(writableBitmap, PageOrientations.Normal, RenderingFlags.Annotations);

        Image.Width = width;
        Image.Height = height;
        Image.Source = writableBitmap;

        SelectionCanvas.Width = width;
        SelectionCanvas.Height = height;

        InkCanvas.Width = width;
        InkCanvas.Height = height;

        Canvas.SetZIndex(Image, 0);

        ConfigureInkCanvas();
    }
}

我尝试使用 DependencyPropertyInkToolbar 发送到 TestControl 但它似乎没有用。

有人可以帮我解决这个问题吗?我不确定是否也可以像这样设置 TargetInkCanvas 。因此,如果对此有更好的方法,我们也将不胜感激。

非常感谢任何帮助。谢谢。

Access InkToolbar placed in parent element from programmatically generated child elements

以上使用 DependencyProperty 传递父级的解决方案 InkToolBar 是正确的。但是,您为 InkToolbarControl 属性.

绑定了错误的值

请直接绑定不带路径的ElementName

<local:TestControl
    x:Name="TestControl"
    Margin="4,4"
    InkToolbarControl="{Binding ElementName=InkToolbar}" />

然后更新 InkCanvasUnprocessedInputPointerExitedInkCanvasUnprocessedInputPointerEntered 流程逻辑。

private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
    InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
    InkToolbarControl.TargetInkCanvas = null;
}

private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
    InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
    InkToolbarControl.TargetInkCanvas = InkCanvas;
}