Przeczytaj w języku angielskim

Udostępnij za pośrednictwem


RichSuggestBox

Jest RichSuggestBox to kombinacja autosuggestBox i RichEditBox , która może udostępniać sugestie na podstawie dostosowywalnych prefiksów. Wybrane sugestie są osadzone i śledzone w dokumencie jako tokeny.

Element RichSuggestBox przypomina kontrolki tekstu często występujące w aplikacjach społecznościowych, w których wpiszesz "@", aby wspomnieć o osobach.

XAML
<!--  Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information.  -->
<Page x:Class="RichSuggestBoxExperiment.Samples.RichSuggestBoxMultiplePrefixesSample"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:controls="using:CommunityToolkit.WinUI.Controls"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:RichSuggestBoxExperiment.Samples"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>
        <DataTemplate x:Key="EmailTemplate">
            <StackPanel Orientation="Horizontal">
                <Border Width="20"
                        Height="20"
                        Background="{ThemeResource AccentFillColorDefaultBrush}"
                        CornerRadius="9999">
                    <TextBlock HorizontalAlignment="Center"
                               VerticalAlignment="Center"
                               FontSize="10"
                               FontWeight="Semibold"
                               Foreground="White"
                               Text="{Binding Initials}" />
                </Border>
                <TextBlock Padding="8,0,0,0"
                           Text="{Binding DisplayName}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="DataTemplate">
            <StackPanel Orientation="Horizontal">
                <SymbolIcon Symbol="{Binding Icon}" />
                <TextBlock Margin="8,0,0,0"
                           Text="{Binding Text}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="TokenTemplate">
            <StackPanel Margin="0,4,0,12"
                        Orientation="Vertical">
                <TextBlock>
                    <Run Text="Text:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding DisplayText}" />
                </TextBlock>
                <TextBlock>
                    <Run Text="Position:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding Position}" />
                </TextBlock>

                <TextBlock>
                    <Run Text="Id:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding Id}" />
                </TextBlock>
            </StackPanel>
        </DataTemplate>

        <local:SuggestionTemplateSelector x:Key="SuggestionTemplateSelector"
                                          Data="{StaticResource DataTemplate}"
                                          Person="{StaticResource EmailTemplate}" />

    </Page.Resources>
    <StackPanel MinWidth="400"
                HorizontalAlignment="Center"
                Spacing="24">
        <controls:RichSuggestBox x:Name="SuggestingBox"
                                 HorizontalAlignment="Stretch"
                                 Header="RichSuggestBox that supports multiple prefixes (@ and #)"
                                 ItemTemplateSelector="{StaticResource SuggestionTemplateSelector}"
                                 Prefixes="@#"
                                 SuggestionChosen="SuggestingBox_SuggestionChosen"
                                 SuggestionRequested="SuggestingBox_SuggestionRequested" />
        <ListView x:Name="TokenListView"
                  HorizontalAlignment="Stretch"
                  ItemTemplate="{StaticResource TokenTemplate}" />
    </StackPanel>
</Page>
C#
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Controls;

#if WINAPPSDK
using Microsoft.UI;
using Microsoft.UI.Text;
#else
using Windows.UI;
using Windows.UI.Text;
#endif

namespace RichSuggestBoxExperiment.Samples;

[ToolkitSample(id: nameof(RichSuggestBoxMultiplePrefixesSample), "RichSuggestBox with multiple prefixes", description: $"A sample for showing how to create and use a {nameof(RichSuggestBox)} with multiple prefixes.")]
public sealed partial class RichSuggestBoxMultiplePrefixesSample : Page
{
    private readonly List<SampleEmailDataType> _emailSamples = new List<SampleEmailDataType>()
        {
            new SampleEmailDataType() { FirstName = "Marcus", FamilyName = "Perryman" },
            new SampleEmailDataType() { FirstName = "Michael", FamilyName = "Hawker" },
            new SampleEmailDataType() { FirstName = "Matt", FamilyName = "Lacey" },
            new SampleEmailDataType() { FirstName = "Alexandre", FamilyName = "Chohfi" },
            new SampleEmailDataType() { FirstName = "Filip", FamilyName = "Wallberg" },
            new SampleEmailDataType() { FirstName = "Shane", FamilyName = "Weaver" },
            new SampleEmailDataType() { FirstName = "Vincent", FamilyName = "Gromfeld" },
            new SampleEmailDataType() { FirstName = "Sergio", FamilyName = "Pedri" },
            new SampleEmailDataType() { FirstName = "Alex", FamilyName = "Wilber" },
            new SampleEmailDataType() { FirstName = "Allan", FamilyName = "Deyoung" },
            new SampleEmailDataType() { FirstName = "Adele", FamilyName = "Vance" },
            new SampleEmailDataType() { FirstName = "Grady", FamilyName = "Archie" },
            new SampleEmailDataType() { FirstName = "Megan", FamilyName = "Bowen" },
            new SampleEmailDataType() { FirstName = "Ben", FamilyName = "Walters" },
            new SampleEmailDataType() { FirstName = "Debra", FamilyName = "Berger" },
            new SampleEmailDataType() { FirstName = "Emily", FamilyName = "Braun" },
            new SampleEmailDataType() { FirstName = "Christine", FamilyName = "Cline" },
            new SampleEmailDataType() { FirstName = "Enrico", FamilyName = "Catteneo" },
            new SampleEmailDataType() { FirstName = "Davit", FamilyName = "Badalyan" },
            new SampleEmailDataType() { FirstName = "Diego", FamilyName = "Siciliani" },
            new SampleEmailDataType() { FirstName = "Raul", FamilyName = "Razo" },
            new SampleEmailDataType() { FirstName = "Miriam", FamilyName = "Graham" },
            new SampleEmailDataType() { FirstName = "Lynne", FamilyName = "Robbins" },
            new SampleEmailDataType() { FirstName = "Lydia", FamilyName = "Holloway" },
            new SampleEmailDataType() { FirstName = "Nestor", FamilyName = "Wilke" },
            new SampleEmailDataType() { FirstName = "Patti", FamilyName = "Fernandez" },
            new SampleEmailDataType() { FirstName = "Pradeep", FamilyName = "Gupta" },
            new SampleEmailDataType() { FirstName = "Joni", FamilyName = "Sherman" },
            new SampleEmailDataType() { FirstName = "Isaiah", FamilyName = "Langer" },
            new SampleEmailDataType() { FirstName = "Irvin", FamilyName = "Sayers" },
            new SampleEmailDataType() { FirstName = "Tung", FamilyName = "Huynh" },
        };

    private readonly List<SampleDataType> _samples = new List<SampleDataType>()
        {
            new SampleDataType() { Text = "Account", Icon = Symbol.Account },
            new SampleDataType() { Text = "Add Friend", Icon = Symbol.AddFriend },
            new SampleDataType() { Text = "Attach", Icon = Symbol.Attach },
            new SampleDataType() { Text = "Attach Camera", Icon = Symbol.AttachCamera },
            new SampleDataType() { Text = "Audio", Icon = Symbol.Audio },
            new SampleDataType() { Text = "Block Contact", Icon = Symbol.BlockContact },
            new SampleDataType() { Text = "Calculator", Icon = Symbol.Calculator },
            new SampleDataType() { Text = "Calendar", Icon = Symbol.Calendar },
            new SampleDataType() { Text = "Camera", Icon = Symbol.Camera },
            new SampleDataType() { Text = "Contact", Icon = Symbol.Contact },
            new SampleDataType() { Text = "Favorite", Icon = Symbol.Favorite },
            new SampleDataType() { Text = "Link", Icon = Symbol.Link },
            new SampleDataType() { Text = "Mail", Icon = Symbol.Mail },
            new SampleDataType() { Text = "Map", Icon = Symbol.Map },
            new SampleDataType() { Text = "Phone", Icon = Symbol.Phone },
            new SampleDataType() { Text = "Pin", Icon = Symbol.Pin },
            new SampleDataType() { Text = "Rotate", Icon = Symbol.Rotate },
            new SampleDataType() { Text = "Rotate Camera", Icon = Symbol.RotateCamera },
            new SampleDataType() { Text = "Send", Icon = Symbol.Send },
            new SampleDataType() { Text = "Tags", Icon = Symbol.Tag },
            new SampleDataType() { Text = "UnFavorite", Icon = Symbol.UnFavorite },
            new SampleDataType() { Text = "UnPin", Icon = Symbol.UnPin },
            new SampleDataType() { Text = "Zoom", Icon = Symbol.Zoom },
            new SampleDataType() { Text = "ZoomIn", Icon = Symbol.ZoomIn },
            new SampleDataType() { Text = "ZoomOut", Icon = Symbol.ZoomOut },
        };
    public RichSuggestBoxMultiplePrefixesSample()
    {
        this.InitializeComponent();
        TokenListView.ItemsSource = SuggestingBox.Tokens;
    }

    private void SuggestingBox_SuggestionChosen(RichSuggestBox sender, SuggestionChosenEventArgs args)
    {
        if (args.Prefix == "#")
        {
            args.Format!.BackgroundColor = Colors.LightSlateGray;
            args.Format.ForegroundColor = Colors.White;
            args.Format.Bold = FormatEffect.On;
            args.DisplayText = ((SampleDataType)args.SelectedItem!).Text;
        }
        else
        {
            args.DisplayText = ((SampleEmailDataType)args.SelectedItem!).DisplayName;
        }
    }

    private void SuggestingBox_SuggestionRequested(RichSuggestBox sender, SuggestionRequestedEventArgs args)
    {
        if (args.Prefix == "#")
        {
            sender.ItemsSource = this._samples.Where(x => x.Text.Contains(args.QueryText!, StringComparison.OrdinalIgnoreCase));
        }
        else
        {
            sender.ItemsSource = this._emailSamples.Where(x => x.DisplayName.Contains(args.QueryText!, StringComparison.OrdinalIgnoreCase));
        }
    }
}

Składnia

XAML
<controls:RichSuggestBox
  PlaceholderText="Leave a comment"
  ItemTemplate="{StaticResource SuggestionTemplate}"
  Prefixes="@#" />

Uwagi

Po wybraniu RichSuggestBox sugestii przypisuje wybrany element unikatowy identyfikator GUID i tekst wyświetlany (dostarczony przez dewelopera), aby utworzyć token. Tekst wyświetlany jest następnie dopełniany spacjami o zerowej szerokości(ZWSP) i wstawiany do dokumentu jako hiperlink przy użyciu identyfikatora jako adresu łącza. Te hiperlinki są śledzone i weryfikowane przy każdej zmianie tekstu.

Tekst tokenu wstawiony do dokumentu ma następujący układ: ZWSP — znak prefiksu — Wyświetlanie tekstu — ZWSP.

Na przykład token "@" jako prefiks i "John Doe" jako tekst wyświetlany jest wstawiany jako:

cs
"\u200b@John Doe\u200b"

Ważne

Tekst tokenu zawiera spację zero widths, które są znakami Unicode.

Uwaga

Aby obsługiwać funkcję Cofnij/Powtórz, przechowuje wszystkie tokeny w kolekcji wewnętrznej nawet wtedy, RichSuggestBox gdy tekst tokenu zostanie usunięty z dokumentu. Token ten jest oznaczony jako nieaktywny i nie jest uwzględniony w kolekcji Tokens . Użyj ClearUndoRedoSuggestionHistory() metody , aby wyczyścić nieaktywne tokeny lub Clear() metodę, aby wyczyścić wszystkie tokeny.

Przykłady

Obsługa wielu typów tokenów

Poniższy przykład tworzy obiekt RichSuggestBox , który może tokenizować obie wzmianki (zapytanie rozpoczyna się od @) i hasztagi (zapytanie rozpoczyna się od #).

XAML
<controls:RichSuggestBox
  PlaceholderText="Leave a comment"
  ItemTemplate="{StaticResource SuggestionTemplate}"
  SuggestionChosen="OnSuggestionChosen"
  SuggestionRequested="OnSuggestionRequested"
  Prefixes="@#" />
cs
private void OnSuggestionChosen(RichSuggestBox sender, SuggestionChosenEventArgs args)
{
  if (args.Prefix == "#")
  {
    // User selected a hashtag item
    args.DisplayText = ((SampleHashtagDataType)args.SelectedItem).Text;
  }
  else
  {
    // User selected a mention item
    args.DisplayText = ((SampleEmailDataType)args.SelectedItem).DisplayName;
  }
}

private void OnSuggestionRequested(RichSuggestBox sender, SuggestionRequestedEventArgs args)
{
  sender.ItemsSource = args.Prefix == "#"
    ? _hashtags.Where(x => x.Text.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase))
    : _emails.Where(x => x.DisplayName.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase));
}

Tylko zwykły tekst

Poniższy przykład tworzy obiekt RichSuggestBox , który umożliwia tylko użytkownikom wprowadzanie zwykłego tekstu. Jedynymi sformatowanymi tekstami w dokumencie są tokeny.

XAML
<controls:RichSuggestBox
  ClipboardCopyFormat="PlainText"
  ClipboardPasteFormat="PlainText"
  DisabledFormattingAccelerators="All" />
XAML
<!--  Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information.  -->
<Page x:Class="RichSuggestBoxExperiment.Samples.RichSuggestBoxPlainTextSample"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:controls="using:CommunityToolkit.WinUI.Controls"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:RichSuggestBoxExperiment.Samples"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>
        <DataTemplate x:Key="EmailTemplate">
            <StackPanel Orientation="Horizontal">
                <Border Width="20"
                        Height="20"
                        Background="{ThemeResource AccentFillColorDefaultBrush}"
                        CornerRadius="9999">
                    <TextBlock HorizontalAlignment="Center"
                               VerticalAlignment="Center"
                               FontSize="10"
                               FontWeight="Semibold"
                               Foreground="White"
                               Text="{Binding Initials}" />
                </Border>
                <TextBlock Padding="8,0,0,0"
                           Text="{Binding DisplayName}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="TokenTemplate">
            <StackPanel Margin="0,4,0,12"
                        Orientation="Vertical">
                <TextBlock>
                    <Run Text="Text:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding DisplayText}" />
                </TextBlock>
                <TextBlock>
                    <Run Text="Position:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding Position}" />
                </TextBlock>

                <TextBlock>
                    <Run Text="Id:" />
                    <Run FontWeight="SemiBold"
                         Text="{Binding Id}" />
                </TextBlock>
            </StackPanel>
        </DataTemplate>

        <local:SuggestionTemplateSelector x:Key="SuggestionTemplateSelector"
                                          Data="{StaticResource DataTemplate}"
                                          Person="{StaticResource EmailTemplate}" />

        <Flyout x:Key="TokenSelectedFlyout">
            <ContentPresenter x:Name="FlyoutPresenter"
                              ContentTemplate="{StaticResource EmailTemplate}" />
        </Flyout>

    </Page.Resources>
    <StackPanel MinWidth="400"
                HorizontalAlignment="Center"
                Spacing="24">

        <controls:RichSuggestBox x:Name="SuggestingBox"
                                 MaxHeight="400"
                                 HorizontalAlignment="Stretch"
                                 ClipboardCopyFormat="PlainText"
                                 ClipboardPasteFormat="PlainText"
                                 DisabledFormattingAccelerators="All"
                                 FlyoutBase.AttachedFlyout="{StaticResource TokenSelectedFlyout}"
                                 Header="Plain text RichSuggestBox with on token pointer over flyout"
                                 ItemTemplate="{StaticResource EmailTemplate}"
                                 Prefixes="@"
                                 SuggestionChosen="SuggestingBox_SuggestionChosen"
                                 SuggestionRequested="SuggestingBox_SuggestionRequested"
                                 TokenPointerOver="SuggestingBox_TokenPointerOver" />


        <ListView x:Name="TokenListView"
                  HorizontalAlignment="Stretch"
                  ItemTemplate="{StaticResource TokenTemplate}" />
    </StackPanel>
</Page>
C#
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Controls;

#if WINAPPSDK
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Text;
#else
using Windows.UI;
using Windows.System;
using Windows.UI.Text;
#endif

namespace RichSuggestBoxExperiment.Samples;

[ToolkitSample(id: nameof(RichSuggestBoxPlainTextSample), "RichSuggestBox with plain text", description: $"A sample for showing how to create and use a {nameof(RichSuggestBox)} with plain text.")]
public sealed partial class RichSuggestBoxPlainTextSample : Page
{
    private DispatcherQueue _dispatcherQueue;
    private readonly List<SampleEmailDataType> _emailSamples = new List<SampleEmailDataType>()
        {
            new SampleEmailDataType() { FirstName = "Marcus", FamilyName = "Perryman" },
            new SampleEmailDataType() { FirstName = "Michael", FamilyName = "Hawker" },
            new SampleEmailDataType() { FirstName = "Matt", FamilyName = "Lacey" },
            new SampleEmailDataType() { FirstName = "Alexandre", FamilyName = "Chohfi" },
            new SampleEmailDataType() { FirstName = "Filip", FamilyName = "Wallberg" },
            new SampleEmailDataType() { FirstName = "Shane", FamilyName = "Weaver" },
            new SampleEmailDataType() { FirstName = "Vincent", FamilyName = "Gromfeld" },
            new SampleEmailDataType() { FirstName = "Sergio", FamilyName = "Pedri" },
            new SampleEmailDataType() { FirstName = "Alex", FamilyName = "Wilber" },
            new SampleEmailDataType() { FirstName = "Allan", FamilyName = "Deyoung" },
            new SampleEmailDataType() { FirstName = "Adele", FamilyName = "Vance" },
            new SampleEmailDataType() { FirstName = "Grady", FamilyName = "Archie" },
            new SampleEmailDataType() { FirstName = "Megan", FamilyName = "Bowen" },
            new SampleEmailDataType() { FirstName = "Ben", FamilyName = "Walters" },
            new SampleEmailDataType() { FirstName = "Debra", FamilyName = "Berger" },
            new SampleEmailDataType() { FirstName = "Emily", FamilyName = "Braun" },
            new SampleEmailDataType() { FirstName = "Christine", FamilyName = "Cline" },
            new SampleEmailDataType() { FirstName = "Enrico", FamilyName = "Catteneo" },
            new SampleEmailDataType() { FirstName = "Davit", FamilyName = "Badalyan" },
            new SampleEmailDataType() { FirstName = "Diego", FamilyName = "Siciliani" },
            new SampleEmailDataType() { FirstName = "Raul", FamilyName = "Razo" },
            new SampleEmailDataType() { FirstName = "Miriam", FamilyName = "Graham" },
            new SampleEmailDataType() { FirstName = "Lynne", FamilyName = "Robbins" },
            new SampleEmailDataType() { FirstName = "Lydia", FamilyName = "Holloway" },
            new SampleEmailDataType() { FirstName = "Nestor", FamilyName = "Wilke" },
            new SampleEmailDataType() { FirstName = "Patti", FamilyName = "Fernandez" },
            new SampleEmailDataType() { FirstName = "Pradeep", FamilyName = "Gupta" },
            new SampleEmailDataType() { FirstName = "Joni", FamilyName = "Sherman" },
            new SampleEmailDataType() { FirstName = "Isaiah", FamilyName = "Langer" },
            new SampleEmailDataType() { FirstName = "Irvin", FamilyName = "Sayers" },
            new SampleEmailDataType() { FirstName = "Tung", FamilyName = "Huynh" },
        };

    public RichSuggestBoxPlainTextSample()
    {
        this.InitializeComponent();
        _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
        TokenListView.ItemsSource = SuggestingBox.Tokens;
    }

    private void SuggestingBox_SuggestionChosen(RichSuggestBox sender, SuggestionChosenEventArgs args)
    {
            args.DisplayText = ((SampleEmailDataType)args.SelectedItem!).DisplayName;
    }

    private void SuggestingBox_SuggestionRequested(RichSuggestBox sender, SuggestionRequestedEventArgs args)
    {
            sender.ItemsSource = this._emailSamples.Where(x => x.DisplayName.Contains(args.QueryText!, StringComparison.OrdinalIgnoreCase));
    }

    private void SuggestingBox_TokenPointerOver(RichSuggestBox sender, RichSuggestTokenPointerOverEventArgs args)
    {
        var flyout = (Flyout)FlyoutBase.GetAttachedFlyout(sender);
        var pointerPosition = args.CurrentPoint!.Position;

        if (flyout?.Content is ContentPresenter cp && sender.TextDocument!.Selection.Type != SelectionType.Normal &&
            (!flyout.IsOpen || cp.Content != args.Token!.Item))
        {
            this._dispatcherQueue.TryEnqueue(() =>
            {
                cp.Content = args.Token!.Item;
                flyout.ShowAt(sender, new FlyoutShowOptions
                {
                    Position = pointerPosition,
                    ExclusionRect = sender.GetRectFromRange(args.Range!),
                    ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway,
                });
            });
        }
    }
}