Home > Software design >  Access InkToolbar placed in parent element from programmatically generated child elements
Access InkToolbar placed in parent element from programmatically generated child elements

Time:09-17

I have a UserControl which contains a InkToolbar. In this control I'm generating another set of UserControls based on user input. In each of those programmatically generated controls, contains a InkCanvas.

This is the parent UserControl & the code behind.

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; }
    }
}

I want to use the InkToolbar which is in the parent control inside the TestControl

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();
    }
}

I tried to use a DependencyProperty to send the InkToolbar to the TestControl but it didn't seem to work.

Could someone please help me with this? I'm not sure whether it is possible to set the TargetInkCanvas like this also. So, if there is a better approach for this any suggestions are also appreciated.

Any help is much appreciated. Thanks.

CodePudding user response:

Access InkToolbar placed in parent element from programmatically generated child elements

Above solution that use DependencyProperty to pass parent InkToolBar is correct. However you bind wrong value for InkToolbarControl property.

Please bind ElementName without path directly.

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

Then update InkCanvasUnprocessedInputPointerExited and InkCanvasUnprocessedInputPointerEntered process logic.

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;
}
  • Related