Compartilhar via


Armazenar e recuperar dados de traço do Windows Ink

Aplicativos do Windows que dão suporte ao Windows Ink podem serializar e desserializar traços de tinta em um arquivo ISF (Formato Serializado de Tinta). O arquivo ISF é uma imagem GIF com metadados adicionais para todas as propriedades e comportamentos de traço de tinta. Os aplicativos que não são compatíveis com tinta podem exibir a imagem GIF estática, incluindo a transparência de fundo do canal alfa.

Observação

ISF é a representação persistente mais compacta da tinta. Ele pode ser incorporado em um formato de documento binário, como um arquivo GIF, ou colocado diretamente na Área de Transferência.

A Especificação de Formato Serializado de Tinta (ISF) pode ser baixada no Centro de Download da Microsoft.

APIs importantes: InkCanvas, Windows.UI.Input.Inking

Salvar traços de tinta em um arquivo

Aqui, demonstramos como salvar traços de tinta desenhados em um controle InkCanvas .

Baixe este exemplo de Salvar e carregar traços de tinta de um arquivo ISF (Formato Serializado de Tinta)

  1. Primeiro, configuramos a interface do usuário.

    A interface do usuário inclui botões "Salvar", "Carregar" e "Limpar" e o 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. Em seguida, definimos alguns comportamentos básicos de entrada de tinta.

    O InkPresenter é configurado para interpretar os dados de entrada da caneta e do mouse como traços de tinta (InputDeviceTypes) e os ouvintes dos eventos de clique nos botões são declarados.

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. Por fim, salvamos a tinta no manipulador de eventos de clique do botão Salvar .

    Um FileSavePicker permite que o usuário selecione o arquivo e o local em que os dados de tinta são salvos.

    Depois que um arquivo é selecionado, abrimos um fluxo IRandomAccessStream definido como ReadWrite.

    Em seguida, chamamos SaveAsync para serializar os traços de tinta gerenciados pelo InkStrokeContainer para o fluxo.

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

Observação

GIF é o único formato de arquivo com suporte para salvar dados de tinta. No entanto, o método LoadAsync (demonstrado na próxima seção) dá suporte a formatos adicionais para compatibilidade com versões anteriores.

Carregar traços de tinta de um arquivo

Aqui, demonstramos como carregar traços de tinta de um arquivo e renderizá-los em um controle InkCanvas .

Baixe este exemplo de Salvar e carregar traços de tinta de um arquivo ISF (Formato Serializado de Tinta)

  1. Primeiro, configuramos a interface do usuário.

    A interface do usuário inclui botões "Salvar", "Carregar" e "Limpar" e o 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. Em seguida, definimos alguns comportamentos básicos de entrada de tinta.

    O InkPresenter é configurado para interpretar os dados de entrada tanto da caneta quanto do mouse como traços de tinta (InputDeviceTypes) e os manipuladores de eventos de clique nos botões são declarados.

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. Por fim, carregamos a tinta no manipulador de eventos de clique do botão Carregar .

    Um FileOpenPicker permite que o usuário selecione o arquivo e o local de onde recuperar os dados de tinta salvos.

    Depois que um arquivo é selecionado, abrimos um fluxo IRandomAccessStream definido como Leitura.

    Em seguida, chamamos LoadAsync para ler, des serializar e carregar os traços de tinta salvos no InkStrokeContainer. Carregar os traços no InkStrokeContainer faz com que o InkPresenter os renderize imediatamente no InkCanvas.

    Observação

    Todos os traços existentes no InkStrokeContainer são limpos antes que novos traços sejam carregados.

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

Observação

GIF é o único formato de arquivo com suporte para salvar dados de tinta. No entanto, o método LoadAsync dá suporte aos seguintes formatos para compatibilidade com versões anteriores.

Formato Description
InkSerializedFormat Especifica a tinta que é mantida usando ISF. Esta é a representação persistente mais compacta da tinta. Ele pode ser inserido em um formato de documento binário ou colocado diretamente na Área de Transferência.
Base64InkSerializedFormat Especifica a tinta que é persistida codificando o ISF como um fluxo base64. Esse formato é fornecido para que a tinta possa ser codificada diretamente em um arquivo XML ou HTML.
Gif (Formato de Intercâmbio de Gráficos) Especifica a tinta que é mantida usando um arquivo GIF que contém ISF como metadados inseridos no arquivo. Isso permite que a tinta seja exibida em aplicativos que não são habilitados para tinta e mantêm sua fidelidade total à tinta quando ela retorna a um aplicativo habilitado para tinta. Esse formato é ideal ao transportar conteúdo de tinta em um arquivo HTML e para torná-lo utilizável por aplicativos de tinta e não tinta.
Base64Gif Especifica a tinta que é persistida usando um GIF reforçado codificado em base64. Esse formato é fornecido quando a tinta deve ser codificada diretamente em um arquivo XML ou HTML para conversão posterior em uma imagem. Um possível uso disso está em um formato XML gerado para conter todas as informações de tinta e usado para gerar HTML por meio de XSLT (Extensible Stylesheet Language Transformations).

Copiar e colar traços de tinta usando a área de transferência

Aqui, demonstramos como usar a área de transferência para transferir traços de tinta entre aplicativos.

Para dar suporte à funcionalidade de área de transferência, os comandos internos de recortar e copiar do InkStrokeContainer exigem que um ou mais traços de tinta sejam selecionados.

Para este exemplo, ativamos a seleção de traçado quando a entrada é modificada com um botão lateral da caneta (ou botão direito do mouse). Para obter um exemplo completo de como implementar a seleção de traço, consulte a entrada de passagem direta para processamento avançado em interações de caneta e estilete.

Baixe este exemplo de Salvar e carregar traços de tinta da área de transferência

  1. Primeiro, configuramos a interface do usuário.

    A interface do usuário inclui botões "Recortar", "Copiar", "Colar" e "Limpar", juntamente com o InkCanvas e uma tela de seleção.

<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. Em seguida, definimos alguns comportamentos básicos de entrada de tinta.

    O InkPresenter é configurado para interpretar dados de entrada de caneta e mouse como traços de tinta (InputDeviceTypes). Os ouvintes dos eventos de clique nos botões, bem como eventos de ponteiro e traço para a funcionalidade de seleção, também são declarados aqui.

    Para obter um exemplo completo de como implementar a seleção de traço, consulte a entrada pass-through para processamento avançado em interações de caneta e estilete.

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. Por fim, depois de adicionar suporte à seleção de traços, implementamos a funcionalidade da área de transferência nos manipuladores de eventos de clique dos botões Recortar, Copiar e Colar.

    Para cortar, primeiro chamamos CopySelectedToClipboard no InkStrokeContainer do InkPresenter.

    Em seguida, chamamos DeleteSelected para remover os traços da tela de tinta.

    Por fim, excluímos todos os traços de seleção da tela de seleção.

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

Para copiar, simplesmente chamamos CopySelectedToClipboard no InkStrokeContainer do InkPresenter.

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

Para colar, chamamos CanPasteFromClipboard para garantir que o conteúdo na área de transferência possa ser colado na tela de tinta.

Nesse caso, chamamos PasteFromClipboard para inserir os traços de tinta da área de transferência no InkStrokeContainer do InkPresenter, que então renderiza os traços no canvas de tinta.

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

Exemplos de tópico

Outros exemplos