RichSuggestBox

RichSuggestBoxAutoSuggestBoxRichEditBox 的组合,可根据可自定义前缀提供建议。 所选建议以令牌的形式嵌入文档中并被跟踪。

RichSuggestBox 类似于社交类应用程序中常见的使用“@”来提及人员的文本控件。

<!--  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>
// 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));
        }
    }
}

语法

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

备注

选择建议后,RichSuggestBox 为所选项目分配唯一的 Guid 和显示文本(由开发人员提供)以组成令牌。 然后,显示文本用零宽度空格 (ZWSP) 填充,插入到文档中作为超链接,并使用标识符作为链接地址。 这些超链接在每次文本更改时都会被跟踪和验证。

插入文档中的令牌文本具有以下布局:ZWSP - 前缀字符 - 显示文本 - ZWSP。

例如,带有“@”前缀和“John Doe”显示文本的令牌插入为:

"\u200b@John Doe\u200b"

重要

令牌文本包含零宽度空格,属于 Unicode 字符。

注意

为支持“撤销/还原”功能,RichSuggestBox 会保留内部集合中的所有令牌,即使从文档中删除令牌文本时也是如此。 这些令牌标记为非活动状态,并且不包括在 Tokens 集合中。 使用 ClearUndoRedoSuggestionHistory() 方法清除非活动令牌或 Clear() 方法清除所有令牌。

示例

处理多个令牌类型

下方示例创建了一个 RichSuggestBox,可以标记提到的内容(查询以 @ 开头)和井号标签(查询以 # 开头)。

<controls:RichSuggestBox
  PlaceholderText="Leave a comment"
  ItemTemplate="{StaticResource SuggestionTemplate}"
  SuggestionChosen="OnSuggestionChosen"
  SuggestionRequested="OnSuggestionRequested"
  Prefixes="@#" />
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));
}

仅纯文本

下方示例创建了一个 RichSuggestBox,仅允许用户输入纯文本。 文档中唯一格式化的文本是令牌。

<controls:RichSuggestBox
  ClipboardCopyFormat="PlainText"
  ClipboardPasteFormat="PlainText"
  DisabledFormattingAccelerators="All" />
<!--  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>
// 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,
                });
            });
        }
    }
}