將 InkToolbar 新增至 Windows 應用程式

有兩個不同的控制項可協助在 Windows 應用程式中提供筆跡:InkCanvasInkToolbar

InkCanvas 控制項提供基本的 Windows Ink 功能。 使用它可將畫筆輸入轉譯為筆墨筆劃 (使用預設顏色和粗細設定) 或擦除筆劃。

如需 InkCanvas 實作的詳細資料,請參閱 Windows 應用程式中的畫筆互動

作為完全透明的重疊,InkCanvas 不會提供任何內建 UI 來設定筆墨筆劃屬性。 如果要變更預設筆跡體驗,讓使用者設定筆墨筆劃屬性並支援其他自訂筆跡書寫功能,有兩種選擇:

  • 在程式碼後置中,使用繫結至 InkCanvas 的基礎 InkPresenter 物件。

    InkPresenter API 支援大量自訂筆跡體驗。 如需詳細資訊,請參閱 Windows 應用程式中的畫筆互動

  • InkToolbar 繫結至 InkCanvas。 InkToolbar 預設提供一組可自訂且可擴充的按鈕,可啟動筆劃大小、筆跡色彩和筆尖等與筆跡相關的功能。

    本主題將討論 InkToolbar。

重要 APIInkCanvas 類別InkToolbar 類別InkPresenter 類別Windows.UI.Input.Inking

預設 InkToolbar

InkToolbar 預設包含用於繪製、清除、醒目提示和顯示尺規 (尺規或量角器) 的按鈕。 根據功能的不同,飛出視窗中提供了其他設定和命令,例如筆跡顏色、筆劃粗細、擦除所有筆跡。

InkToolbar
預設 Windows Ink 工具列

若要將預設 InkToolbar 新增至筆跡應用程式,只需將它放置於 InkCanvas 所在頁面,並建立這兩個控制項的關聯。

  1. 在 MainPage.xaml 中,宣告容器物件 (在本範例中,我們使用控制項) 做為筆跡介面。
  2. 將 InkCanvas 物件宣告為容器的子系。 (InkCanvas 大小繼承繼承容器大小。)
  3. 宣告 InkToolbar,並使用 TargetInkCanvas 屬性將它繫結至 InkCanvas。

注意

確保在 InkCanvas 之後宣告 InkToolbar。 如果沒有,InkCanvas 重疊會將 InkToolbar 轉譯為無法存取的狀態。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header"
                   Text="Basic ink sample"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   Margin="10,0,0,0" />
    </StackPanel>
    <Grid Grid.Row="1">
        <Image Source="Assets\StoreLogo.png" />
        <InkCanvas x:Name="inkCanvas" />
        <InkToolbar x:Name="inkToolbar"
          VerticalAlignment="Top"
          TargetInkCanvas="{x:Bind inkCanvas}" />
    </Grid>
</Grid>

基本自訂

在本節中,我們將討論一些基本的 Windows Ink 工具列自訂案例。

指定位置和方向

當您將筆跡工具列新增至應用程式時,可以使用工具列的預設位置和方向,或根據應用程式或使用者要求進行設定。

XAML

透過工具列的 VerticalAlignmentHorizontalAlignmentOrientation 屬性,明確指定工具列的位置和方向。

預設 顯式
Default ink toolbar location and orientation Explicit ink toolbar location and orientation
Windows Ink 工具列預設位置和方向 Windows Ink 工具列明確位置和方向

以下是在 XAML 中明確設定筆跡工具列位置和方向的程式碼。

<InkToolbar x:Name="inkToolbar" 
    VerticalAlignment="Center" 
    HorizontalAlignment="Right" 
    Orientation="Vertical" 
    TargetInkCanvas="{x:Bind inkCanvas}" />

根據使用者喜好設定或裝置狀態初始化

在某些情況下,您可能想要根據使用者喜好設定或裝置狀態來設定筆跡工具列的位置和方向。 以下的範例示範如何根據左手或右手書寫喜好設定 (透過 [設定] > [裝置] > [畫筆 & Windows Ink] > [畫筆] > [選擇使用哪隻手書寫]) 來設定筆跡工具列的位置和方向。

Dominant hand setting
慣用手設定

可以透過 Windows.UI.ViewManagement 的 HandPreference 屬性查詢此設定,並根據傳回的值設定 HorizontalAlignment。 在此範例中,我們為慣用左手的使用者將工具列放置在應用程式左側,並為慣用右手的使用者將工具列放置在右側。

筆跡工具列位置和方向範例 (基本) 下載此範例

public MainPage()
{
    this.InitializeComponent();

    Windows.UI.ViewManagement.UISettings settings = 
        new Windows.UI.ViewManagement.UISettings();
    HorizontalAlignment alignment = 
        (settings.HandPreference == 
            Windows.UI.ViewManagement.HandPreference.LeftHanded) ? 
            HorizontalAlignment.Left : HorizontalAlignment.Right;
    inkToolbar.HorizontalAlignment = alignment;
}

根據使用者或裝置狀態動態調整

也可以使用繫結,根據使用者喜好設定、裝置設定或裝置狀態的變更來更新 UI。 在以下範例中,我們以上一個範例為基礎示範如何使用繫結、ViewMOdel 物件和 INotifyPropertyChanged 介面,根據裝置方向動態調整筆跡工具列位置。

筆跡工具列位置和方向範例 (動態) 下載此範例

  1. 首先,讓我們新增 ViewModel。

    1. 將新資料夾新增至您的專案,並將其命名為 ViewModels

    2. 將新類別新增至 ViewModels 資料夾 (在本範例中,我們稱之為 InkToolbarSnippetHostViewModel.cs)。

      注意

      我們使用 Singleton 模式,因為在應用程式的生命週期內,我們只需要一個此類型的物件

    3. using System.ComponentModel 命名空間新增至檔案。

    4. 新增名為 instance 的靜態成員變數,以及名為 Instance 的靜態唯讀屬性。 將建構函式設為私有,以確保只能透過 Instance 屬性存取此類別。

      注意

      這個類別繼承自 INotifyPropertyChanged 介面,該介面用來通知用戶端 (通常是繫結用戶端) 屬性值已變更。 我們將使用此方法來處理裝置方向的變更 (我們將擴展此程序碼,並在稍後的步驟中進一步說明)。

      using System.ComponentModel;
      
      namespace locationandorientation.ViewModels
      {
          public class InkToolbarSnippetHostViewModel : INotifyPropertyChanged
          {
              private static InkToolbarSnippetHostViewModel instance;
      
              public static InkToolbarSnippetHostViewModel Instance
              {
                  get
                  {
                      if (null == instance)
                      {
                          instance = new InkToolbarSnippetHostViewModel();
                      }
                      return instance;
                  }
              }
          }
      
          private InkToolbarSnippetHostViewModel() { }
      }
      
    5. 在 InkToolbarSnippetHostViewModel 類別中新增兩個布林值屬性:LeftHandedLayout (與先前僅適用於 XAML 的範例功能相同) 和 PortraitLayout (裝置的方向)。

      注意

      可以設定 PortraitLayout 屬性,並加入 PropertyChanged 事件的定義。

      public bool LeftHandedLayout
      {
          get
          {
              bool leftHandedLayout = false;
              Windows.UI.ViewManagement.UISettings settings =
                  new Windows.UI.ViewManagement.UISettings();
              leftHandedLayout = (settings.HandPreference ==
                  Windows.UI.ViewManagement.HandPreference.LeftHanded);
              return leftHandedLayout;
          }
      }
      
      public bool portraitLayout = false;
      public bool PortraitLayout
      {
          get
          {
              Windows.UI.ViewManagement.ApplicationViewOrientation winOrientation = 
                  Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Orientation;
              portraitLayout = 
                  (winOrientation == 
                      Windows.UI.ViewManagement.ApplicationViewOrientation.Portrait);
              return portraitLayout;
          }
          set
          {
              if (value.Equals(portraitLayout)) return;
              portraitLayout = value;
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PortraitLayout"));
          }
      }
      
  2. 現在,讓我們為專案新增幾個轉換器類別。 每個類別都包含 Convert 物件,該物件會傳回對齊值 (HorizontalAlignmentVerticalAlignment)。

    1. 將新資料夾新增至您的專案,並將其命名為 Converters

    2. 將兩個新類別新增至 Converters 資料夾 (在本範例中,我們會將其命名為 HorizontalAlignmentFromHandednessConverter.csVerticalAlignmentFromAppViewConverter.cs)。

    3. 為每個檔案新增 using Windows.UI.Xamlusing Windows.UI.Xaml.Data 命名空間。

    4. 將每個類別變更為 public,並指定它實作 IValueConverter 介面。

    5. ConvertConvertBack 方法新增至每個檔案,如下所示 (我們未實作 ConvertBack 方法)。

      • HorizontalAlignmentFromHandednessConverter 會將筆跡工具列放置在應用程式右側,供慣用右手的使用者使用,放置在應用程式左側供慣用左手的使用者使用。
      using System;
      
      using Windows.UI.Xaml;
      using Windows.UI.Xaml.Data;
      
      namespace locationandorientation.Converters
      {
          public class HorizontalAlignmentFromHandednessConverter : IValueConverter
          {
              public object Convert(object value, Type targetType,
                  object parameter, string language)
              {
                  bool leftHanded = (bool)value;
                  HorizontalAlignment alignment = HorizontalAlignment.Right;
                  if (leftHanded)
                  {
                      alignment = HorizontalAlignment.Left;
                  }
                  return alignment;
              }
      
              public object ConvertBack(object value, Type targetType,
                  object parameter, string language)
              {
                  throw new NotImplementedException();
              }
          }
      }
      
      • VerticalAlignmentFromAppViewConverter 會將筆跡工具列放置在應用程式的中央供垂直螢幕使用者使用,放置在應用程式頂部供橫向螢幕使用者使用 (目的是提高可用性,但這只是供示範用途的隨意選擇)。
      using System;
      
      using Windows.UI.Xaml;
      using Windows.UI.Xaml.Data;
      
      namespace locationandorientation.Converters
      {
          public class VerticalAlignmentFromAppViewConverter : IValueConverter
          {
              public object Convert(object value, Type targetType,
                  object parameter, string language)
              {
                  bool portraitOrientation = (bool)value;
                  VerticalAlignment alignment = VerticalAlignment.Top;
                  if (portraitOrientation)
                  {
                      alignment = VerticalAlignment.Center;
                  }
                  return alignment;
              }
      
              public object ConvertBack(object value, Type targetType,
                  object parameter, string language)
              {
                  throw new NotImplementedException();
              }
          }
      }
      
  3. 現在開啟 MainPage.xaml.cs 檔案。

    1. using using locationandorientation.ViewModels 新增至命名空間清單以建立與 ViewModel 的關聯。
    2. using Windows.UI.ViewManagement 新增至命名空間清單以便能夠接聽裝置方向的變更。
    3. 新增 WindowSizeChangedEventHandler 程式碼。
    4. 將檢視的 DataContext 設定為 InkToolbarSnippetHostViewModel 類別的單一執行個體。
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    
    using locationandorientation.ViewModels;
    using Windows.UI.ViewManagement;
    
    namespace locationandorientation
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
    
                Window.Current.SizeChanged += (sender, args) =>
                {
                    ApplicationView currentView = ApplicationView.GetForCurrentView();
    
                    if (currentView.Orientation == ApplicationViewOrientation.Landscape)
                    {
                        InkToolbarSnippetHostViewModel.Instance.PortraitLayout = false;
                    }
                    else if (currentView.Orientation == ApplicationViewOrientation.Portrait)
                    {
                        InkToolbarSnippetHostViewModel.Instance.PortraitLayout = true;
                    }
                };
    
                DataContext = InkToolbarSnippetHostViewModel.Instance;
            }
        }
    }
    
  4. 接下來,開啟 MainPage.xaml 檔案。

    1. xmlns:converters="using:locationandorientation.Converters" 新增至 Page 元素以繫結到轉換器。

      <Page
      x:Class="locationandorientation.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:locationandorientation"
      xmlns:converters="using:locationandorientation.Converters"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
      
    2. 新增 PageResources 元素,並指定對轉換器的參考。

      <Page.Resources>
          <converters:HorizontalAlignmentFromHandednessConverter x:Key="HorizontalAlignmentConverter"/>
          <converters:VerticalAlignmentFromAppViewConverter x:Key="VerticalAlignmentConverter"/>
      </Page.Resources>
      
    3. 新增 InkCanvas 和 InkToolbar 元素,並繫結 InkToolbar 的 VerticalAlignment 和 HorizontalAlignment 屬性。

      <InkCanvas x:Name="inkCanvas" />
      <InkToolbar x:Name="inkToolbar" 
                  VerticalAlignment="{Binding PortraitLayout, Converter={StaticResource VerticalAlignmentConverter} }" 
                  HorizontalAlignment="{Binding LeftHandedLayout, Converter={StaticResource HorizontalAlignmentConverter} }" 
                  Orientation="Vertical" 
                  TargetInkCanvas="{x:Bind inkCanvas}" />
      
  5. 返回 InkToolbarSnippetHostViewModel.cs 檔案,將 PortraitLayoutLeftHandedLayout 布林值屬性新增至 InkToolbarSnippetHostViewModel 類別,並支援在屬性值變更時重新繫結 PortraitLayout

    public bool LeftHandedLayout
    {
        get
        {
            bool leftHandedLayout = false;
            Windows.UI.ViewManagement.UISettings settings =
                new Windows.UI.ViewManagement.UISettings();
            leftHandedLayout = (settings.HandPreference ==
                Windows.UI.ViewManagement.HandPreference.LeftHanded);
            return leftHandedLayout;
        }
    }
    
    public bool portraitLayout = false;
    public bool PortraitLayout
    {
        get
        {
            Windows.UI.ViewManagement.ApplicationViewOrientation winOrientation = 
                Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Orientation;
            portraitLayout = 
                (winOrientation == 
                    Windows.UI.ViewManagement.ApplicationViewOrientation.Portrait);
            return portraitLayout;
        }
        set
        {
            if (value.Equals(portraitLayout)) return;
            portraitLayout = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PortraitLayout"));
        }
    }
    
    #region INotifyPropertyChanged Members
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnPropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
    
    #endregion
    

您現在應該有一個筆跡應用程式,既可以同時適應使用者的慣用手部喜好設定,又能動態回應使用者裝置的方向。

指定選取的按鈕

Pencil button selected at initialization
在初始化時選取鉛筆按鈕的 Windows Ink 工具列

預設情況下,啟動應用程式並初始化工具列時,會選取第一個 (或最左側) 按鈕。 在預設的 Windows Ink 工具列中,這是鋼珠筆按鈕。

因為架構會定義內建按鈕的順序,因此第一個按鈕可能不是您想要預設啟動的畫筆或工具。

可以覆寫此預設行為,並在工具列上指定選取的按鈕。

在本範例中,我們將在初始化預設工具列時選取鉛筆按鈕並啟動 (而不是鋼珠筆)。

  1. 使用前一個範例中 InkCanvas 和 InkToolbar 的 XAML 宣告。
  2. 在程式碼後置中,設定 InkToolbar 物件的 Loaded 事件的處理常式。
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// Here, we set up InkToolbar event listeners.
/// </summary>
public MainPage_CodeBehind()
{
    this.InitializeComponent();
    // Add handlers for InkToolbar events.
    inkToolbar.Loaded += inkToolbar_Loaded;
}
  1. Loaded 事件的處理常式中:

    1. 取得內建 InkToolbarPencilButton 的參考。

    傳遞 GetToolButton 方法中的 InkToolbarTool.Pencil 物件,將傳回 InkToolbarPencilButtonInkToolbarToolButton 物件。

    1. ActiveTool 設定為上一個步驟中傳回的物件。
/// <summary>
/// Handle the Loaded event of the InkToolbar.
/// By default, the active tool is set to the first tool on the toolbar.
/// Here, we set the active tool to the pencil button.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void inkToolbar_Loaded(object sender, RoutedEventArgs e)
{
    InkToolbarToolButton pencilButton = inkToolbar.GetToolButton(InkToolbarTool.Pencil);
    inkToolbar.ActiveTool = pencilButton;
}

指定內建按鈕

Specific buttons included at initialization
初始化時所包含的特定按鈕

如前所述,Windows Ink 工具列包含一組預設的內建按鈕。 這些按鈕會依以下順序顯示 (從左至右):

在本範例中,我們只使用內建的鋼珠筆、鉛筆和橡皮擦按鈕來初始化工具列。

可以使用 XAML 或程式碼後置來執行此動作。

XAML

修改第一個範例中 InkCanvas 和 InkToolbar 的 XAML 宣告。

注意

按鈕會按架構定義的順序新增至工具列,而不是此處指定的順序。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header"
                   Text="Basic ink sample"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   Margin="10,0,0,0" />
    </StackPanel>
    <Grid Grid.Row="1">
        <Image Source="Assets\StoreLogo.png" />
        <!-- Clear the default InkToolbar buttons by setting InitialControls to None. -->
        <!-- Set the active tool to the pencil button. -->
        <InkCanvas x:Name="inkCanvas" />
        <InkToolbar x:Name="inkToolbar"
                    VerticalAlignment="Top"
                    TargetInkCanvas="{x:Bind inkCanvas}"
                    InitialControls="None">
            <!--
             Add only the ballpoint pen, pencil, and eraser.
             Note that the buttons are added to the toolbar in the order
             defined by the framework, not the order we specify here.
            -->
            <InkToolbarEraserButton />
            <InkToolbarBallpointPenButton />
            <InkToolbarPencilButton/>
        </InkToolbar>
    </Grid>
</Grid>

程式碼後置

  1. 使用第一個範例中 InkCanvas 和 InkToolbar 的 XAML 宣告。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header"
                   Text="Basic ink sample"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   Margin="10,0,0,0" />
    </StackPanel>
    <Grid Grid.Row="1">
        <Image Source="Assets\StoreLogo.png" />
        <InkCanvas x:Name="inkCanvas" />
        <InkToolbar x:Name="inkToolbar"
        VerticalAlignment="Top"
        TargetInkCanvas="{x:Bind inkCanvas}" />
    </Grid>
</Grid>
  1. 在程式碼後置中,設定 InkToolbar 物件的 Loading 事件的處理常式。
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// Here, we set up InkToolbar event listeners.
/// </summary>
public MainPage_CodeBehind()
{
    this.InitializeComponent();
    // Add handlers for InkToolbar events.
    inkToolbar.Loading += inkToolbar_Loading;
}
  1. InitialControls 設定為「None」。
  2. 為應用程式所需的按鈕建立物件參考。 在這裡,我們只新增 InkToolbarBallpointPenButtonInkToolbarPencilButtonInkToolbarEraserButton

注意

按鈕會按架構定義的順序新增至工具列,而不是此處指定的順序。

  1. 將按鈕新增至 InkToolbar。
/// <summary>
/// Handles the Loading event of the InkToolbar.
/// Here, we identify the buttons to include on the InkToolbar.
/// </summary>
/// <param name="sender">The InkToolbar</param>
/// <param name="args">The InkToolbar event data.
/// If there is no event data, this parameter is null</param>
private void inkToolbar_Loading(FrameworkElement sender, object args)
{
    // Clear all built-in buttons from the InkToolbar.
    inkToolbar.InitialControls = InkToolbarInitialControls.None;

    // Add only the ballpoint pen, pencil, and eraser.
    // Note that the buttons are added to the toolbar in the order
    // defined by the framework, not the order we specify here.
    InkToolbarBallpointPenButton ballpoint = new InkToolbarBallpointPenButton();
    InkToolbarPencilButton pencil = new InkToolbarPencilButton();
    InkToolbarEraserButton eraser = new InkToolbarEraserButton();
    inkToolbar.Children.Add(eraser);
    inkToolbar.Children.Add(ballpoint);
    inkToolbar.Children.Add(pencil);
}

自訂按鈕和筆跡書寫功能

可自訂並擴充 InkToolbar 提供的按鈕集合 (以及相關聯的筆跡書寫功能)。

InkToolbar 由兩組不同的按鈕類型組成:

  1. 一組「工具」按鈕,包含內建繪圖、清除和醒目提示按鈕。 此處新增了自訂畫筆和工具。

注意:功能選擇是互斥的。

  1. 一組「切換」按鈕,包含內建尺規按鈕。 在這裡新增自訂切換開關。

注意:這些功能並不會互斥,可以與其他活動工具同時使用。

根據您的應用程式和所需的筆跡功能,您可以將以下任意按鈕 (繫結到您的自訂筆跡功能) 新增至 InkToolbar:

  • 自訂畫筆 – 由主應用程式定義筆跡調色盤板和筆尖屬性 (例如形狀、旋轉和大小) 的畫筆。
  • 自訂工具 – 由主應用程式定義的非畫筆工具。
  • 自訂切換 – 將應用程式定義功能的狀態設定為開啟或關閉。 開啟後,該功能會與使用中的工具搭配使用。

注意:您無法變更內建按鈕的顯示順序。 預設顯示順序為:鋼珠筆、鉛筆、螢光筆、橡皮擦和尺規。 自訂畫筆會附加到最後一個預設畫筆,自訂工具按鈕會新增在最後一個畫筆按鈕和橡皮擦按鈕之間,自訂切換按鈕會新增在尺規按鈕之後。 (自訂按鈕按照指定的順序新增。)

自訂畫筆

可以建立自訂畫筆 (透過自訂畫筆按鈕啟動),並定義筆跡調色盤和筆尖屬性,例如:形狀、旋轉和大小。

Custom calligraphic pen button
自訂書法畫筆按鈕

在本範例中,我們定義一個寬筆尖的自訂畫筆,用於啟用基本的書法筆墨筆劃。 我們也在按鈕飛出視窗處自訂調色盤中的一組筆刷。

程式碼後置

首先,定義自訂畫筆並在程式碼後置中指定繪圖屬性。 我們稍後會從 XAML 引用這個自訂畫筆。

  1. 在方案總管中,以滑鼠右鍵按一下專案,然後依序選取 [新增] -> [新增項目]。
  2. 在 [Visual C#] -> [程式碼] 底下,新增類別檔案,並將其命名為 CalligraphicPen.cs。
  3. 在 Calligraphic.cs 中,將預設使用區塊替換為下列內容:
using System.Numerics;
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
  1. 指定 CalligraphicPen 類別衍生自 InkToolbarCustomPen
class CalligraphicPen : InkToolbarCustomPen
{
}
  1. 覆寫 CreateInkDrawingAttributesCore 以指定您自己的筆刷和筆劃大小。
class CalligraphicPen : InkToolbarCustomPen
{
    protected override InkDrawingAttributes
      CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
    {
    }
}
  1. 建立 InkDrawingAttributes 物件,並設定 筆尖形狀筆尖旋轉筆劃大小筆跡色彩
class CalligraphicPen : InkToolbarCustomPen
{
    protected override InkDrawingAttributes
      CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
    {
        InkDrawingAttributes inkDrawingAttributes =
          new InkDrawingAttributes();
        inkDrawingAttributes.PenTip = PenTipShape.Circle;
        inkDrawingAttributes.Size =
          new Windows.Foundation.Size(strokeWidth, strokeWidth * 20);
        SolidColorBrush solidColorBrush = brush as SolidColorBrush;
        if (solidColorBrush != null)
        {
            inkDrawingAttributes.Color = solidColorBrush.Color;
        }
        else
        {
            inkDrawingAttributes.Color = Colors.Black;
        }

        Matrix3x2 matrix = Matrix3x2.CreateRotation(45);
        inkDrawingAttributes.PenTipTransform = matrix;

        return inkDrawingAttributes;
    }
}

XAML

接下來,我們會在 MainPage.xaml 中新增自訂畫筆的必要參考。

  1. 我們會宣告本機頁面資源字典,以建立 CalligraphicPen.cs 中所定義自訂畫筆 (CalligraphicPen) 的參考,以及 自訂畫筆支援的筆刷集合 (CalligraphicPenPalette)。
<Page.Resources>
    <!-- Add the custom CalligraphicPen to the page resources. -->
    <local:CalligraphicPen x:Key="CalligraphicPen" />
    <!-- Specify the colors for the palette of the custom pen. -->
    <BrushCollection x:Key="CalligraphicPenPalette">
        <SolidColorBrush Color="Blue" />
        <SolidColorBrush Color="Red" />
    </BrushCollection>
</Page.Resources>
  1. 接著,我們會新增具有子系 InkToolbarCustomPenButton 元素的 InkToolbar。

自訂畫筆按鈕包含頁面資源中宣告的兩個靜態資源參考:CalligraphicPenCalligraphicPenPalette

我們也指定筆劃大小滑桿的範圍 (MinStrokeWidthMaxStrokeWidthSelectedStrokeWidth)、選取的筆刷 (SelectedBrushIndex),以及自訂畫筆按鈕的圖示 (SymbolIcon)。

<Grid Grid.Row="1">
    <InkCanvas x:Name="inkCanvas" />
    <InkToolbar x:Name="inkToolbar"
                VerticalAlignment="Top"
                TargetInkCanvas="{x:Bind inkCanvas}">
        <InkToolbarCustomPenButton
            CustomPen="{StaticResource CalligraphicPen}"
            Palette="{StaticResource CalligraphicPenPalette}"
            MinStrokeWidth="1" MaxStrokeWidth="3" SelectedStrokeWidth="2"
            SelectedBrushIndex ="1">
            <SymbolIcon Symbol="Favorite" />
            <InkToolbarCustomPenButton.ConfigurationContent>
                <InkToolbarPenConfigurationControl />
            </InkToolbarCustomPenButton.ConfigurationContent>
        </InkToolbarCustomPenButton>
    </InkToolbar>
</Grid>

自訂切換

可以建立自訂切換 (透過自訂切換按鈕啟動) 以將應用程式定義功能的狀態設定為開啟或關閉。 開啟後,該功能會與使用中的工具搭配使用。

在本範例中,我們定義自訂切換按鈕,以啟用觸控輸入的筆跡 (觸控筆跡預設為未啟用)。

注意

如果需要支援觸控筆跡,建議使用 CustomToggleButton 以及本範例中指定的圖示和工具提示來啟用筆跡。

觸控輸入通常用於直接操作物件或應用程式 UI。 為了示範啟用觸控筆跡前後的行為差異,我們會將 InkCanvas 放置在 ScrollViewer 容器中,並將 ScrollViewer 的尺寸設定為小於 InkCanvas。

當應用程式啟動時,只支援畫筆筆跡,並且會使用觸控來平移或縮放筆跡介面。 啟用觸控筆跡後,無法使用觸控輸入移動瀏覽或放大筆跡介面。

注意

請參閱適用於 InkCanvasInkToolbarInkCanvas UX 指導方針之筆跡控制項。 以下建議與這個範例相關:

  • 主動式畫筆能為 InkToolbar 以及一般筆跡書寫帶來最好的體驗。 但是,如果您的應用程式需要,可以支援使用滑鼠和觸控書寫。
  • 如果支援使用觸控輸入的手寫筆跡,建議您針對切換按鈕 (包含「觸控書寫」工具提示) 使用「Segoe MLD2 Assets」字型的「ED5F」圖示。

XAML

  1. 首先,我們會使用 Click 事件接聽程式來宣告 InkToolbarCustomToggleButton 元素 (toggleButton),以指定事件處理常式 (Toggle_Custom)。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel Grid.Row="0" 
                x:Name="HeaderPanel" 
                Orientation="Horizontal">
        <TextBlock x:Name="Header" 
                   Text="Basic ink sample" 
                   Style="{ThemeResource HeaderTextBlockStyle}" 
                   Margin="10" />
    </StackPanel>

    <ScrollViewer Grid.Row="1" 
                  HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
        
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <InkToolbar Grid.Row="0" 
                        Margin="10"
                        x:Name="inkToolbar" 
                        VerticalAlignment="Top"
                        TargetInkCanvas="{x:Bind inkCanvas}">
                <InkToolbarCustomToggleButton 
                x:Name="toggleButton" 
                Click="CustomToggle_Click" 
                ToolTipService.ToolTip="Touch Writing">
                    <SymbolIcon Symbol="{x:Bind TouchWritingIcon}"/>
                </InkToolbarCustomToggleButton>
            </InkToolbar>
            
            <ScrollViewer Grid.Row="1" 
                          Height="500"
                          Width="500"
                          x:Name="scrollViewer" 
                          ZoomMode="Enabled" 
                          MinZoomFactor=".1" 
                          VerticalScrollMode="Enabled" 
                          VerticalScrollBarVisibility="Auto" 
                          HorizontalScrollMode="Enabled" 
                          HorizontalScrollBarVisibility="Auto">
                
                <Grid x:Name="outputGrid" 
                      Height="1000"
                      Width="1000"
                      Background="{ThemeResource SystemControlBackgroundChromeWhiteBrush}">
                    <InkCanvas x:Name="inkCanvas"/>
                </Grid>
                
            </ScrollViewer>
        </Grid>
    </ScrollViewer>
</Grid>

程式碼後置

  1. 在前面的程式碼片段中,我們在自訂切換按鈕上宣告 Click 事件接聽程式和處理常式 (Toggle_Custom),用於觸控筆跡 (toggleButton)。 此處理常式只需透過 InkPresenter 的 InputDeviceTypes 屬性切換 CoreInputDeviceTypes.Touch 的支援。

    我們也使用 SymbolIcon 元素和 {x:Bind} 標記延伸來指定按鈕的圖示,以將它繫結至程式碼後置檔案中定義的欄位 (TouchWritingIcon)。

    以下程式碼片段包括 Click 事件處理常式和 TouchWritingIcon 的定義。

namespace Ink_Basic_InkToolbar
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage_AddCustomToggle : Page
    {
        Symbol TouchWritingIcon = (Symbol)0xED5F;

        public MainPage_AddCustomToggle()
        {
            this.InitializeComponent();
        }

        // Handler for the custom toggle button that enables touch inking.
        private void CustomToggle_Click(object sender, RoutedEventArgs e)
        {
            if (toggleButton.IsChecked == true)
            {
                inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Touch;
            }
            else
            {
                inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;
            }
        }
    }
}

自訂工具

可以建立自訂工具按鈕來叫用應用程式定義的非畫筆工具。

預設情況下,InkPresenter 將所有輸入作為筆墨筆劃或擦除筆劃來處理。 這包括由輔助硬體功能 (例如:筆身按鈕、滑鼠右鍵或類似機制) 修改的輸入。 但是,可以將 InkPresenter 設定為讓特定輸入保留為未處理,然後可以傳遞到應用程式以進行自訂處理。

在此範例中,我們定義自訂工具按鈕,選取該按鈕時會導致後續的筆劃處理並轉譯為選取套索 (虛線),而不是筆跡。 選取區域範圍內的所有筆墨筆劃都會設定為「已選取」。

注意

請參閱適用於 InkCanvas 和 InkToolbar 的 InkCanvas UX 指導方針之筆跡控制項。 以下建議與這個範例相關:

  • 如果提供筆劃選取項目,建議您針對工具按鈕 (包含「選取工具」工具提示) 使用「Segoe MLD2 Assets」字型的「EF20」圖示。

XAML

  1. 首先,我們會使用 Click 事件接聽程式來宣告 InkToolbarCustomToolButton(customToolButton 元素 (customToolButton),以指定設定筆劃選取項目的事件處理常式 (customToolButton_Click)。 (我們也新增一組用於複製、剪下和貼上筆劃選取項目的按鈕。)

  2. 我們也新增 Canvas 元素來繪製選取的筆劃。 使用個別圖層繪製選取的筆劃可確保 InkCanvas 及其內容保持不變。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
        <TextBlock x:Name="Header" 
                   Text="Basic ink sample" 
                   Style="{ThemeResource HeaderTextBlockStyle}" 
                   Margin="10,0,0,0" />
    </StackPanel>
    <StackPanel x:Name="ToolPanel" Orientation="Horizontal" Grid.Row="1">
        <InkToolbar x:Name="inkToolbar" 
                    VerticalAlignment="Top" 
                    TargetInkCanvas="{x:Bind inkCanvas}">
            <InkToolbarCustomToolButton 
                x:Name="customToolButton" 
                Click="customToolButton_Click" 
                ToolTipService.ToolTip="Selection tool">
                <SymbolIcon Symbol="{x:Bind SelectIcon}"/>
            </InkToolbarCustomToolButton>
        </InkToolbar>
        <Button x:Name="cutButton" 
                Content="Cut" 
                Click="cutButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
        <Button x:Name="copyButton" 
                Content="Copy"  
                Click="copyButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
        <Button x:Name="pasteButton" 
                Content="Paste"  
                Click="pasteButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
    </StackPanel>
    <Grid Grid.Row="2" x:Name="outputGrid" 
              Background="{ThemeResource SystemControlBackgroundChromeWhiteBrush}" 
              Height="Auto">
        <!-- Canvas for displaying selection UI. -->
        <Canvas x:Name="selectionCanvas"/>
        <!-- Canvas for displaying ink. -->
        <InkCanvas x:Name="inkCanvas" />
    </Grid>
</Grid>

程式碼後置

  1. 然後,處理 MainPage.xaml.cs 程式碼後置檔案中 InkToolbarCustomToolButton 的 Click 事件。

    此處理常式將 InkPresenter 設定為將未處理的輸入傳遞到應用程式。

    如需更詳細的程式碼逐步解說:請參閱 Windows 應用程式中的手寫筆互動與 Windows Ink中進階處理一節的傳遞查詢輸入。

    我們也使用 SymbolIcon 元素和 {x:Bind} 標記延伸來指定按鈕的圖示,以將它繫結至程式碼後置檔案中定義的欄位 (SelectIcon)。

    以下程式碼片段包括 Click 事件處理常式和 SelectIcon 的定義。

namespace Ink_Basic_InkToolbar
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage_AddCustomTool : Page
    {
        // Icon for custom selection tool button.
        Symbol SelectIcon = (Symbol)0xEF20;

        // Stroke selection tool.
        private Polyline lasso;
        // Stroke selection area.
        private Rect boundingRect;

        public MainPage_AddCustomTool()
        {
            this.InitializeComponent();

            // Listen for new ink or erase strokes to clean up selection UI.
            inkCanvas.InkPresenter.StrokeInput.StrokeStarted +=
                StrokeInput_StrokeStarted;
            inkCanvas.InkPresenter.StrokesErased +=
                InkPresenter_StrokesErased;
        }

        private void customToolButton_Click(object sender, RoutedEventArgs e)
        {
            // By default, the InkPresenter processes input modified by 
            // a secondary affordance (pen barrel button, right mouse 
            // button, or similar) as ink.
            // To pass through modified input to the app for custom processing 
            // on the app UI thread instead of the background ink thread, set 
            // InputProcessingConfiguration.RightDragAction to LeaveUnprocessed.
            inkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction =
                InkInputRightDragAction.LeaveUnprocessed;

            // Listen for unprocessed pointer events from modified input.
            // The input is used to provide selection functionality.
            inkCanvas.InkPresenter.UnprocessedInput.PointerPressed +=
                UnprocessedInput_PointerPressed;
            inkCanvas.InkPresenter.UnprocessedInput.PointerMoved +=
                UnprocessedInput_PointerMoved;
            inkCanvas.InkPresenter.UnprocessedInput.PointerReleased +=
                UnprocessedInput_PointerReleased;
        }

        // Handle new ink or erase strokes to clean up selection UI.
        private void StrokeInput_StrokeStarted(
            InkStrokeInput sender, Windows.UI.Core.PointerEventArgs args)
        {
            ClearSelection();
        }

        private void InkPresenter_StrokesErased(
            InkPresenter sender, InkStrokesErasedEventArgs args)
        {
            ClearSelection();
        }

        private void cutButton_Click(object sender, RoutedEventArgs e)
        {
            inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
            inkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
            ClearSelection();
        }

        private void copyButton_Click(object sender, RoutedEventArgs e)
        {
            inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
        }

        private void pasteButton_Click(object sender, RoutedEventArgs e)
        {
            if (inkCanvas.InkPresenter.StrokeContainer.CanPasteFromClipboard())
            {
                inkCanvas.InkPresenter.StrokeContainer.PasteFromClipboard(
                    new Point(0, 0));
            }
            else
            {
                // Cannot paste from clipboard.
            }
        }

        // Clean up selection UI.
        private void ClearSelection()
        {
            var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
            foreach (var stroke in strokes)
            {
                stroke.Selected = false;
            }
            ClearBoundingRect();
        }

        private void ClearBoundingRect()
        {
            if (selectionCanvas.Children.Any())
            {
                selectionCanvas.Children.Clear();
                boundingRect = Rect.Empty;
            }
        }

        // Handle unprocessed pointer events from modifed input.
        // The input is used to provide selection functionality.
        // Selection UI is drawn on a canvas under the InkCanvas.
        private void UnprocessedInput_PointerPressed(
            InkUnprocessedInput sender, PointerEventArgs args)
        {
            // Initialize a selection lasso.
            lasso = new Polyline()
            {
                Stroke = new SolidColorBrush(Windows.UI.Colors.Blue),
                StrokeThickness = 1,
                StrokeDashArray = new DoubleCollection() { 5, 2 },
            };

            lasso.Points.Add(args.CurrentPoint.RawPosition);

            selectionCanvas.Children.Add(lasso);
        }

        private void UnprocessedInput_PointerMoved(
            InkUnprocessedInput sender, PointerEventArgs args)
        {
            // Add a point to the lasso Polyline object.
            lasso.Points.Add(args.CurrentPoint.RawPosition);
        }

        private void UnprocessedInput_PointerReleased(
            InkUnprocessedInput sender, PointerEventArgs args)
        {
            // Add the final point to the Polyline object and 
            // select strokes within the lasso area.
            // Draw a bounding box on the selection canvas 
            // around the selected ink strokes.
            lasso.Points.Add(args.CurrentPoint.RawPosition);

            boundingRect =
                inkCanvas.InkPresenter.StrokeContainer.SelectWithPolyLine(
                    lasso.Points);

            DrawBoundingRect();
        }

        // Draw a bounding rectangle, on the selection canvas, encompassing 
        // all ink strokes within the lasso area.
        private void DrawBoundingRect()
        {
            // Clear all existing content from the selection canvas.
            selectionCanvas.Children.Clear();

            // Draw a bounding rectangle only if there are ink strokes 
            // within the lasso area.
            if (!((boundingRect.Width == 0) ||
                (boundingRect.Height == 0) ||
                boundingRect.IsEmpty))
            {
                var rectangle = new Rectangle()
                {
                    Stroke = new SolidColorBrush(Windows.UI.Colors.Blue),
                    StrokeThickness = 1,
                    StrokeDashArray = new DoubleCollection() { 5, 2 },
                    Width = boundingRect.Width,
                    Height = boundingRect.Height
                };

                Canvas.SetLeft(rectangle, boundingRect.X);
                Canvas.SetTop(rectangle, boundingRect.Y);

                selectionCanvas.Children.Add(rectangle);
            }
        }
    }
}

自訂筆跡轉譯

預設情況下,筆跡輸入會在低延遲的背景執行緒中處理,並在繪製時轉譯為「已修改」。 完成筆劃 (畫筆或手指抬起或放開滑鼠按鍵) 時,將在 UI 執行緒中處理筆劃,並將「原始」轉譯到 InkCanvas 層 (替換應用程式內容上方的已修改筆跡)。

筆跡平台能讓您覆寫此行為,並透過自訂原始筆跡輸入來完全自訂筆跡體驗。

如需自訂原始的詳細資訊,請參閱 Windows 應用程式中的手寫筆互動與 Windows Ink

注意

自訂原始和 InkToolbar
如果應用程式使用自訂原始實作覆寫 InkPresenter 的預設筆跡轉譯行為,則轉譯筆墨筆劃已不再供 InkToolbar 使用,而且 InkToolbar 的內建擦除命令無法如預期工作。 若要提供擦除功能,必須處理所有指標事件、對每個筆劃執行點擊測試,並覆寫內建的「擦除所有筆跡」命令。

主題範例

其他範例