如何添加删除按钮来删除令牌

时间:2015-07-01 06:24:00

标签: wpf richtextbox token tokenize

我正在尝试创建一个控件,它将接受我们在stackoverflow中使用的标签类功能。我正在尝试自定义RichTextBox以实现此功能。我已将以下链接作为参考。

http://blog.pixelingene.com/2010/10/tokenizing-control-convert-text-to-tokens/

如何添加删除按钮以删除令牌?

1 个答案:

答案 0 :(得分:2)

您可以通过向DataTemplate添加Button来实现上述功能。然后将InLineUIContainer分配给按钮的Tag proeprty。然后在按钮单击事件上,您可以从RichTextBox中删除InLineUIContainer。 请参阅以下代码以获取修改后的DataTemplate和Tokenizer Control代码。

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TokenizingControlTester" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="TokenizingControlTester.MainWindow"
    Title="Testing TokenizingControl" Height="244" Width="525" Icon="14-tag.png">
<Window.Resources>
    <DataTemplate x:Key="NameTokenTemplate">
        <DataTemplate.Resources>
            <Storyboard x:Key="OnLoaded1">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
                    <SplineDoubleKeyFrame KeyTime="0" Value="0"/>
                    <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </DataTemplate.Resources>
        <Border x:Name="border" BorderBrush="#FF7E7E7E" BorderThickness="2" CornerRadius="5" Height="Auto" d:DesignWidth="139" d:DesignHeight="40" Padding="5,3" Margin="3,0,3,3">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFFFD0A0" Offset="0"/>
                    <GradientStop Color="#FFAB5600" Offset="1"/>
                </LinearGradientBrush>
            </Border.Background>
            <Grid HorizontalAlignment="Left" Width="Auto">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.21*"/>
                    <ColumnDefinition Width="0.79*"/>
                    <ColumnDefinition Width="0.79*"/>
                </Grid.ColumnDefinitions>
                <Image HorizontalAlignment="Right" Source="14-tag.png" Stretch="None" Width="Auto" Grid.Column="0" VerticalAlignment="Center"/>
                <TextBlock TextWrapping="NoWrap" Text="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1" Margin="10,0,0,0" FontWeight="Bold"/>
                <Button Name="btnClose" Content="X" Grid.Column="2"  />
            </Grid>
        </Border>
        <DataTemplate.Triggers>
            <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                <BeginStoryboard Storyboard="{StaticResource OnLoaded1}"/>
            </EventTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</Window.Resources>
<DockPanel>
    <DockPanel.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FFB8CCF7" Offset="0"/>
            <GradientStop Color="#FF313131" Offset="1"/>
        </LinearGradientBrush>
    </DockPanel.Background>

    <Grid DockPanel.Dock="Top" Margin="5,5,5,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.059*"/>
            <ColumnDefinition Width="0.941*"/>
        </Grid.ColumnDefinitions>
        <Image Source="08-chat.png" Stretch="None" VerticalAlignment="Center" d:LayoutOverrides="Width"/>

        <TextBlock TextWrapping="Wrap" Text="Enter any text ending with semi-colon &quot;;&quot; and have it immediately converted to a Token" FontSize="16" d:LayoutOverrides="Width, Height" Grid.Column="1" Margin="7,0,0,0"/>

    </Grid>

    <local:TokenizingControl x:Name="Tokenizer"  IsDocumentEnabled="True"
                             VerticalAlignment="Top" Margin="5,11,5,0" TokenTemplate="{DynamicResource NameTokenTemplate}" FontSize="16" MinHeight="40" VerticalScrollBarVisibility="Auto">
        <FlowDocument>
            <Paragraph><Run /></Paragraph>
        </FlowDocument>
    </local:TokenizingControl>

</DockPanel>

 public class TokenizingControl : RichTextBox
{
    public static readonly DependencyProperty TokenTemplateProperty =
        DependencyProperty.Register("TokenTemplate", typeof (DataTemplate), typeof (TokenizingControl));

    public DataTemplate TokenTemplate
    {
        get { return (DataTemplate) GetValue(TokenTemplateProperty); }
        set { SetValue(TokenTemplateProperty, value); }
    }

    public Func<string, object> TokenMatcher { get; set; }

    public TokenizingControl()
    {
        TextChanged += OnTokenTextChanged;
    }

    private void OnTokenTextChanged(object sender, TextChangedEventArgs e)
    {
        var text = CaretPosition.GetTextInRun(LogicalDirection.Backward);
        if (TokenMatcher != null)
        {
            var token = TokenMatcher(text);
            if (token != null)
            {
                ReplaceTextWithToken(text, token);
            }
        }
    }

    private void ReplaceTextWithToken(string inputText, object token)
    {
        // Remove the handler temporarily as we will be modifying tokens below, causing more TextChanged events
        TextChanged -= OnTokenTextChanged;

        var para = CaretPosition.Paragraph;

        var matchedRun = para.Inlines.FirstOrDefault(inline =>
        {
            var run = inline as Run;
            return (run != null && run.Text.EndsWith(inputText));
        }) as Run;
        if (matchedRun != null) // Found a Run that matched the inputText
        {
            var tokenContainer = CreateTokenContainer(inputText, token);
            para.Inlines.InsertBefore(matchedRun, tokenContainer);


            // Remove only if the Text in the Run is the same as inputText, else split up
            if (matchedRun.Text == inputText)
            {
                para.Inlines.Remove(matchedRun);
            }
            else // Split up
            {
                var index = matchedRun.Text.IndexOf(inputText) + inputText.Length;
                var tailEnd = new Run(matchedRun.Text.Substring(index));
                para.Inlines.InsertAfter(matchedRun, tailEnd);
                para.Inlines.Remove(matchedRun);
            }
        }

        TextChanged += OnTokenTextChanged;
    }

    private Dictionary<int, object> dic = new Dictionary<int, object>();

    private InlineUIContainer CreateTokenContainer(string inputText, object token)
    {
        // Note: we are not using the inputText here, but could be used in future

        var presenter = new ContentPresenter()
        {              

            Content = token,
            ContentTemplate = TokenTemplate,
        };
        presenter.ApplyTemplate();
        Button bt = TokenTemplate.FindName("btnClose", presenter) as Button;
        bt.Click += bt_Click;

        InlineUIContainer inlin=  new InlineUIContainer(presenter) { BaselineAlignment = BaselineAlignment.TextBottom };
        bt.Tag = inlin;
        // BaselineAlignment is needed to align with Run
       return inlin;
    }

    void bt_Click(object sender, RoutedEventArgs e)
    {
        Button btn = sender as Button;
        InlineUIContainer inputText = btn.Tag as InlineUIContainer;
        Paragraph pr = null;
        foreach (var block in this.Document.Blocks)
        {
            if (block is Paragraph)
            {
                var paragraph = block as Paragraph;

                if(paragraph.Inlines.Contains(inputText))
                {
                    pr = paragraph;
                }
            }
        }
        pr.Inlines.Remove(inputText);           
    }
}