儲存與檢索 Windows Ink 筆劃資料

支援 Windows Ink 的 Windows 應用程式可以將墨水筆劃序列化與反序列化成 Ink 序列化格式(ISF)檔案。 ISF 檔案是一張 GIF 圖片,並附有所有墨水筆觸屬性與行為的額外元資料。 未支援數位墨水的應用程式也能查看靜態 GIF 影像,包括 alpha 通道背景透明效果。

備註

ISF 是最緊湊且持久的墨水表示方式。 它可以嵌入於二進位文件格式中,例如 GIF 檔案,或直接放置在剪貼簿上。

墨水序列化格式(ISF)規範可從 Microsoft 下載中心下載。

Important API: InkCanvas, Windows.UI.Input.Inking

將墨線筆劃儲存到檔案中

在此,我們示範如何保存在InkCanvas控制項上繪製的筆劃。

下載此範例來自 保存和載入墨跡,來自 Ink Serialized Format(ISF) 檔案

  1. 首先,我們設定了介面。

    介面包含「儲存」、「載入」和「清除」按鈕,以及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. 接著我們設定一些基本的墨水輸入行為。

    InkPresenter 設定為將筆與滑鼠的輸入資料解讀為墨水筆劃(InputDeviceTypes),並宣告按鈕點擊事件的監聽器。

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. 最後,我們將墨水儲存在 儲存 按鈕的點擊事件處理程式中。

    FileSavePicker 允許使用者同時選擇檔案及儲存墨水資料的位置。

    選擇檔案後,我們會開啟一個設定為 ReadWrite 的 IRandomAccessStream 串流。

    接著我們呼叫 SaveAsync,將由 InkStrokeContainer 管理的墨跡序列化到串流中。

// 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 是唯一支援儲存墨水資料的檔案格式。 然而, LoadAsync 方法(在下一節示範)確實支援額外格式以促進向下相容。

從檔案載入墨水筆劃

在這裡,我們示範如何從檔案載入墨線筆觸,並用 InkCanvas 控制鍵渲染。

下載此範例來自 保存和載入墨跡,來自 Ink Serialized Format(ISF) 檔案

  1. 首先,我們設定了介面。

    介面包含「儲存」、「載入」和「清除」按鈕,以及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. 接著我們設定一些基本的墨水輸入行為。

    InkPresenter 設定為將筆與滑鼠的輸入資料解讀為墨水筆劃(InputDeviceTypes),並宣告按鈕點擊事件的監聽器。

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. 最後,我們將墨水載入到 載入 按鈕的點擊事件處理程式中。

    FileOpenPicker 允許使用者選擇檔案及取回儲存墨水資料的位置。

    選取檔案後,我們會開啟一個IRandomAccessStream 串流,設定為 Read。

    接著我們呼叫 LoadAsync 來讀取、反序列化,並將儲存的墨水筆劃載入 InkStrokeContainer。 將筆劃載入 InkStrokeContainer,會讓 InkPresenter 立即渲染到 InkCanvas。

    備註

    InkStrokeContainer 中所有現有的筆劃都會被清除,然後才會載入新的筆劃。

// 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 是唯一支援儲存墨水資料的檔案格式。 不過, LoadAsync 方法支援以下格式以維持向下相容。

格式 說明
InkSerializedFormat 指定使用 ISF 保存的墨水。 這是墨水中最緊湊且持久的表示方式。 它可以嵌入在二進位文件格式中,或直接放在剪貼簿上。
Base64InkSerializedFormat(序列化格式) 指定透過將 ISF 編碼為 base64 串流來持久化的墨水。 此格式可直接將墨水編碼成 XML 或 HTML 檔案。
GIF 指定以包含 ISF 作為檔案中嵌入元資料的 GIF 檔案來持久化的墨水。 這使得墨水能在未支援墨水功能的應用程式中被查看,並在返回支援墨水功能的應用程式時保持墨水的完整真實性。 此格式非常適合在 HTML 檔案中傳輸墨水內容,並讓墨水與非墨水應用程式都能使用。
Base64Gif 指定以 base64 編碼強化 GIF 持續保存的墨水。 此格式用於墨水直接編碼成 XML 或 HTML 檔案,以便日後轉換成影像。 其可能用途是生成包含所有墨水資訊的 XML 格式,並透過可擴充樣式表語言轉換(XSLT)產生 HTML。

用剪貼板複製貼上墨水筆劃

在這裡,我們示範如何利用剪貼簿在應用程式間轉移墨水筆劃。

為了支援剪貼簿功能,內建的 InkStrokeContainer 剪切與複製指令需要選擇一個或多個墨水筆劃。

在這個例子中,當輸入被筆桿按鈕(或滑鼠右鍵)修改時,我們啟用筆劃選擇。 欲了解如何實作筆劃選擇的完整範例,請參閱筆與觸控筆互動中的進階處理直通式輸入。

從 從剪貼簿儲存和載入墨蹟下載此範例

  1. 首先,我們設定了介面。

    介面包含「剪切」、「複製」、「貼上」和「清除」按鈕,以及 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="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. 接著我們設定一些基本的墨水輸入行為。

    InkPresenter 被設定為將筆與滑鼠的輸入資料解讀為筆劃(InputDeviceTypes)。 按鈕點擊事件的監聽器,以及用於選取功能的指標與筆劃事件也在此宣告。

    欲了解如何實作筆劃選擇的完整範例,請參閱筆與觸控筆互動中的進階處理直通式輸入。

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. 最後,在新增筆劃選取支援後,我們在 剪切、 複製和 貼上 按鈕的點擊事件處理程式中實作剪貼板功能。

    對於剪下,我們首先在 InkPresenter 的 InkStrokeContainer 上呼叫 CopySelectedToClipboard。

    接著我們呼叫 DeleteSelect ,將筆觸從墨水畫布中移除。

    最後,將選取畫布中的所有選取筆劃刪除。

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

對於複製,我們只需在InkPresenter的InkStrokeContainer上呼叫CopySelectedToClipboard。

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

對於貼上,我們會呼叫 CanPasteFromClipboard ,以確保剪貼板上的內容能貼到墨水畫布上。

如果是這樣,我們會呼叫 PasteFromClipboard,將剪貼板的墨水筆劃插入 InkStrokeContainer 的 InkPresenter,然後將筆觸渲染到墨水畫布上。

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

主題範例

其他取樣