儲存和抓取 Windows Ink 筆劃資料Store and retrieve Windows Ink stroke data

支援 Windows Ink 的 UWP app 可以將筆跡筆觸序列化和還原序列化至筆跡序列化格式 (ISF) 檔案。UWP apps that support Windows Ink can serialize and deserialize ink strokes to an Ink Serialized Format (ISF) file. ISF 檔案是包含所有筆跡筆觸屬性和行為的其他中繼資料的 GIF 映像。The ISF file is a GIF image with additional metadata for all ink stroke properties and behaviors. 未啟用筆墨功能的 app 可以檢視靜態的 GIF 影像,包括 Alpha 色板背景透明度。Apps that are not ink-enabled, can view the static GIF image, including alpha-channel background transparency.

注意

ISF 是最簡單且易於保留格式的筆跡表示法。ISF is the most compact persistent representation of ink. 它可以內嵌到二進位文件格式中 (例如 GIF 檔案),或直接放置到剪貼簿上。It can be embedded within a binary document format, such as a GIF file, or placed directly on the Clipboard.

重要 APIInkCanvasWindows.UI.Input.InkingImportant APIs: InkCanvas, Windows.UI.Input.Inking

將筆墨筆劃儲存為檔案Save ink strokes to a file

我們將在此處示範如何儲存在 InkCanvas 控制項上繪製的筆墨筆劃。Here, we demonstrate how to save ink strokes drawn on an InkCanvas control.

從從筆墨序列化格式(ISF)檔案儲存和載入筆跡筆劃下載此範例Download this sample from Save and load ink strokes from an Ink Serialized Format (ISF) file

  1. 一開始先設定 UI。First, we set up the UI.

    UI 包含 [儲存]、[載入] 和 [清除] 按鈕,以及 InkCanvasThe UI includes "Save", "Load", and "Clear" buttons, and the InkCanvas.

<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 store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnSave" 
                    Content="Save" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnLoad" 
                    Content="Load" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="50,0,10,0"/>
        </StackPanel>
        <Grid Grid.Row="1">
            <InkCanvas x:Name="inkCanvas" />
        </Grid>
    </Grid>
  1. 然後設定一些基本的筆墨輸入行為。We then set some basic ink input behaviors.

    InkPresenter 已設定為會將來自畫筆和滑鼠的輸入資料解譯為筆墨筆劃 (InputDeviceTypes),而且會針對按鈕上的 click 事件宣告接聽程式。The InkPresenter is configured to interpret input data from both pen and mouse as ink strokes (InputDeviceTypes), and listeners for the click events on the buttons are declared.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to initiate save.
        btnSave.Click += btnSave_Click;
        // Listen for button click to initiate load.
        btnLoad.Click += btnLoad_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;
    }
  1. 最後,會在 [儲存] 按鈕的 click 事件處理常式中儲存筆劃。Finally, we save the ink in the click event handler of the Save button.

    FileSavePicker 讓使用者能夠選取儲存筆劃資料的檔案和位置。A FileSavePicker lets the user select both the file and the location where the ink data is saved.

    選取檔案之後,我們會開啟已設為 ReadWriteIRandomAccessStream 資料流。Once a file is selected, we open an IRandomAccessStream stream set to ReadWrite.

    接著呼叫 SaveAsync,將 InkStrokeContainer 管理的筆墨筆劃序列化至資料流。We then call SaveAsync to serialize the ink strokes managed by the InkStrokeContainer to the stream.

// Save ink data to a file.
    private async void btnSave_Click(object sender, RoutedEventArgs e)
    {
        // Get all strokes on the InkCanvas.
        IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();

        // Strokes present on ink canvas.
        if (currentStrokes.Count > 0)
        {
            // Let users choose their ink file using a file picker.
            // Initialize the picker.
            Windows.Storage.Pickers.FileSavePicker savePicker = 
                new Windows.Storage.Pickers.FileSavePicker();
            savePicker.SuggestedStartLocation = 
                Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
            savePicker.FileTypeChoices.Add(
                "GIF with embedded ISF", 
                new List<string>() { ".gif" });
            savePicker.DefaultFileExtension = ".gif";
            savePicker.SuggestedFileName = "InkSample";

            // Show the file picker.
            Windows.Storage.StorageFile file = 
                await savePicker.PickSaveFileAsync();
            // When chosen, picker returns a reference to the selected file.
            if (file != null)
            {
                // Prevent updates to the file until updates are 
                // finalized with call to CompleteUpdatesAsync.
                Windows.Storage.CachedFileManager.DeferUpdates(file);
                // Open a file stream for writing.
                IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
                // Write the ink strokes to the output stream.
                using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
                {
                    await inkCanvas.InkPresenter.StrokeContainer.SaveAsync(outputStream);
                    await outputStream.FlushAsync();
                }
                stream.Dispose();

                // Finalize write so other apps can update file.
                Windows.Storage.Provider.FileUpdateStatus status =
                    await Windows.Storage.CachedFileManager.CompleteUpdatesAsync(file);

                if (status == Windows.Storage.Provider.FileUpdateStatus.Complete)
                {
                    // File saved.
                }
                else
                {
                    // File couldn't be saved.
                }
            }
            // User selects Cancel and picker returns null.
            else
            {
                // Operation cancelled.
            }
        }
    }

注意

GIF 是儲存筆墨資料時唯一支援的檔案格式。GIF is the only file format supported for saving ink data. 不過,LoadAsync 方法 (請見下一節的示範) 可以支援用於回溯相容性的其他格式。However, the LoadAsync method (demonstrated in the next section) does support additional formats for backward compatibility.

從檔案載入筆墨筆劃Load ink strokes from a file

我們將在此處示範如何從檔案載入筆墨筆劃,並在 InkCanvas 控制項上轉譯它們。Here, we demonstrate how to load ink strokes from a file and render them on an InkCanvas control.

從從筆墨序列化格式(ISF)檔案儲存和載入筆跡筆劃下載此範例Download this sample from Save and load ink strokes from an Ink Serialized Format (ISF) file

  1. 一開始先設定 UI。First, we set up the UI.

    UI 包含 [儲存]、[載入] 和 [清除] 按鈕,以及 InkCanvasThe UI includes "Save", "Load", and "Clear" buttons, and the InkCanvas.

<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 store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnSave" 
                    Content="Save" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnLoad" 
                    Content="Load" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="50,0,10,0"/>
        </StackPanel>
        <Grid Grid.Row="1">
            <InkCanvas x:Name="inkCanvas" />
        </Grid>
    </Grid>
  1. 然後設定一些基本的筆墨輸入行為。We then set some basic ink input behaviors.

    InkPresenter 已設定為會將來自畫筆和滑鼠的輸入資料解譯為筆墨筆劃 (InputDeviceTypes),而且會針對按鈕上的 click 事件宣告接聽程式。The InkPresenter is configured to interpret input data from both pen and mouse as ink strokes (InputDeviceTypes), and listeners for the click events on the buttons are declared.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to initiate save.
        btnSave.Click += btnSave_Click;
        // Listen for button click to initiate load.
        btnLoad.Click += btnLoad_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;
    }
  1. 最後,會在 [載入] 按鈕的 click 事件處理常式中載入筆墨。Finally, we load the ink in the click event handler of the Load button.

    FileOpenPicker 讓使用者能夠選取要從中抓取已儲存之筆墨資料的檔案和位置。A FileOpenPicker lets the user select both the file and the location from where to retrieve the saved ink data.

    選取檔案之後,我們會開啟已設為 ReadIRandomAccessStream 資料流。Once a file is selected, we open an IRandomAccessStream stream set to Read.

    接著呼叫 LoadAsync,來讀取並還原序列化已儲存的筆墨筆劃,然後載入 InkStrokeContainerWe then call LoadAsync to read, de-serialize, and load the saved ink strokes into the InkStrokeContainer. 將筆劃載入 InkStrokeContainer,會導致 InkPresenter 立即將它們轉譯到 InkCanvasLoading the strokes into the InkStrokeContainer causes the InkPresenter to immediately render them to the InkCanvas.

    注意

    InkStrokeContainer 中的所有現有筆劃都會先清除,然後再載入新的筆劃。All existing strokes in the InkStrokeContainer are cleared before new strokes are loaded.

// Load ink data from a file.
private async void btnLoad_Click(object sender, RoutedEventArgs e)
{
    // Let users choose their ink file using a file picker.
    // Initialize the picker.
    Windows.Storage.Pickers.FileOpenPicker openPicker =
        new Windows.Storage.Pickers.FileOpenPicker();
    openPicker.SuggestedStartLocation =
        Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
    openPicker.FileTypeFilter.Add(".gif");
    // Show the file picker.
    Windows.Storage.StorageFile file = await openPicker.PickSingleFileAsync();
    // User selects a file and picker returns a reference to the selected file.
    if (file != null)
    {
        // Open a file stream for reading.
        IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
        // Read from file.
        using (var inputStream = stream.GetInputStreamAt(0))
        {
            await inkCanvas.InkPresenter.StrokeContainer.LoadAsync(inputStream);
        }
        stream.Dispose();
    }
    // User selects Cancel and picker returns null.
    else
    {
        // Operation cancelled.
    }
}

注意

GIF 是儲存筆墨資料時唯一支援的檔案格式。GIF is the only file format supported for saving ink data. 不過,LoadAsync 方法可以支援用於回溯相容性的下列格式。However, the LoadAsync method does support the following formats for backward compatibility.

格式Format 描述Description
InkSerializedFormatInkSerializedFormat 指定使用 ISF 保留的筆墨。Specifies ink that is persisted using ISF. 這是最簡單且易於保留格式的筆墨表示法。This is the most compact persistent representation of ink. 它可以內嵌到二進位文件格式中,或直接放置到剪貼簿上。It can be embedded within a binary document format or placed directly on the Clipboard.
Base64InkSerializedFormatBase64InkSerializedFormat 指定透過將 ISF 編碼為 base64 資料流來保留的筆墨。Specifies ink that is persisted by encoding the ISF as a base64 stream. 提供這個格式是為了讓筆墨能夠直接在 XML 或 HTML 檔案中進行編碼。This format is provided so ink can be encoded directly in an XML or HTML file.
GIFGif 指定使用 GIF 檔案保留的筆墨,這種檔案包含做為中繼資料內嵌在檔案中的 ISF。Specifies ink that is persisted by using a GIF file that contains ISF as metadata embedded within the file. 這可讓筆墨可在沒有啟用筆墨功能的應用程式中加以檢視,並且在其返回啟用筆墨功能的應用程式時仍完全不失真。This enables ink to be viewed in applications that are not ink-enabled and maintain its full ink fidelity when it returns to an ink-enabled application. 這個格式適合用來在 HTML 檔案中傳輸筆墨內容,讓筆墨和非筆墨應用程式都可以使用該內容。This format is ideal when transporting ink content within an HTML file and for making it usable by ink and non-ink applications.
Base64GifBase64Gif 指定使用 base64 編碼保護之 GIF 保留的筆墨。Specifies ink that is persisted by using a base64-encoded fortified GIF. 提供這個格式是為了在 XML 或 HTML 檔案中直接編碼筆墨,以便稍後轉換為影像。This format is provided when ink is to be encoded directly in an XML or HTML file for later conversion into an image. 可能的使用情況是產生包含所有筆墨資訊的 XML 格式,並用來透過可延伸樣式表語言轉換 (Extensible Stylesheet Language Transformations,XSLT) 來產生 HTML。A possible use of this is in an XML format generated to contain all ink information and used to generate HTML through Extensible Stylesheet Language Transformations (XSLT).

複製筆墨筆劃並貼上剪貼簿Copy and paste ink strokes with the clipboard

我們將在此處示範如何使用剪貼簿,在 app 之間傳輸筆墨筆劃。Here, we demonstrate how to use the clipboard to transfer ink strokes between apps.

若要支援剪貼簿功能,內建的 InkStrokeContainer 剪下和複製命令會要求選取一或多個筆墨筆劃。To support clipboard functionality, the built-in InkStrokeContainer cut and copy commands require one or more ink strokes be selected.

在這個範例中,我們將在使用畫筆筆身按鈕 (或滑鼠右鍵按鈕) 修改輸入時啟用筆劃選取項目。For this example, we enable stroke selection when input is modified with a pen barrel button (or right mouse button). 如需如何實作筆劃選取項目的完整範例,請參閱畫筆和手寫筆互動中的<傳入輸入以進行進階處理>。For a complete example of how to implement stroke selection, see Pass-through input for advanced processing in Pen and stylus interactions.

從[[儲存] 和 從剪貼簿載入筆跡筆劃] 下載此範例Download this sample from Save and load ink strokes from the clipboard

  1. 一開始先設定 UI。First, we set up the UI.

    UI 包含 [剪下]、[複製]、[貼上] 和 [清除] 按鈕,以及 InkCanvas 和選取項目畫布。The UI includes "Cut", "Copy", "Paste", and "Clear" buttons, along with the InkCanvas and a selection canvas.

<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="tbHeader" 
                       Text="Basic ink store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnCut" 
                    Content="Cut" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnCopy" 
                    Content="Copy" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnPaste" 
                    Content="Paste" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="20,0,10,0"/>
        </StackPanel>
        <Grid x:Name="gridCanvas" Grid.Row="1">
            <!-- Canvas for displaying selection UI. -->
            <Canvas x:Name="selectionCanvas"/>
            <!-- Inking area -->
            <InkCanvas x:Name="inkCanvas"/>
        </Grid>
    </Grid>
  1. 然後設定一些基本的筆墨輸入行為。We then set some basic ink input behaviors.

    InkPresenter 已設定為可將來自畫筆和滑鼠的輸入資料解譯為筆墨筆劃 (InputDeviceTypes)。The InkPresenter is configured to interpret input data from both pen and mouse as ink strokes (InputDeviceTypes). 此處也會宣告按鈕上適用於 click 事件的接聽程式,以及適用於選取功能的指標和筆劃事件。Listeners for the click events on the buttons as well as pointer and stroke events for selection functionality are also declared here.

    如需如何實作筆劃選取項目的完整範例,請參閱畫筆和手寫筆互動中的<傳入輸入以進行進階處理>。For a complete example of how to implement stroke selection, see Pass-through input for advanced processing in Pen and stylus interactions.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to cut ink strokes.
        btnCut.Click += btnCut_Click;
        // Listen for button click to copy ink strokes.
        btnCopy.Click += btnCopy_Click;
        // Listen for button click to paste ink strokes.
        btnPaste.Click += btnPaste_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;

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

        // Listen for new ink or erase strokes to clean up selection UI.
        inkCanvas.InkPresenter.StrokeInput.StrokeStarted +=
            StrokeInput_StrokeStarted;
        inkCanvas.InkPresenter.StrokesErased +=
            InkPresenter_StrokesErased;
    }
  1. 最後,在新增筆劃選取項目支援之後,我們會在 [剪下][複製][貼上] 按鈕的 click 事件處理常式中實作剪貼簿功能。Finally, after adding stroke selection support, we implement clipboard functionality in the click event handlers of the Cut, Copy, and Paste buttons.

    針對剪下功能,我們會先在 InkPresenterInkStrokeContainer 上呼叫 CopySelectedToClipboardFor cut, we first call CopySelectedToClipboard on the InkStrokeContainer of the InkPresenter.

    接著呼叫 DeleteSelected 來移除筆墨畫布上的筆劃。We then call DeleteSelected to remove the strokes from the ink canvas.

    最後,從選取項目畫布中刪除所有選取筆劃。Finally, we delete all selection strokes from the selection canvas.

private void btnCut_Click(object sender, RoutedEventArgs e)
    {
        inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
        inkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
        ClearSelection();
    }
// Clean up selection UI.
    private void ClearSelection()
    {
        var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
        foreach (var stroke in strokes)
        {
            stroke.Selected = false;
        }
        ClearDrawnBoundingRect();
    }

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

針對複製功能,我們只需在 InkPresenterInkStrokeContainer 上呼叫 CopySelectedToClipboardFor copy, we simply call CopySelectedToClipboard on the InkStrokeContainer of the InkPresenter.

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

針對貼上,我們呼叫 CanPasteFromClipboard 確認剪貼簿上的內容可貼到筆跡畫布。For paste, we call CanPasteFromClipboard to ensure that the content on the clipboard can be pasted to the ink canvas.

若可以的話,我們便會呼叫 PasteFromClipboard 將剪貼簿的筆墨筆劃插入 IntPresenterInkStrokeContainer,接著便會將筆劃轉譯到筆跡畫布。If so, we call PasteFromClipboard to insert the clipboard ink strokes into the InkStrokeContainer of the InkPresenter, which then renders the strokes to the ink canvas.

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

主題範例Topic samples

其他範例Other samples