我正在尝试创建一个控件,它将接受我们在stackoverflow中使用的标签类功能。我正在尝试自定义RichTextBox以实现此功能。我已将以下链接作为参考。
http://blog.pixelingene.com/2010/10/tokenizing-control-convert-text-to-tokens/
如何添加删除按钮以删除令牌?
答案 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 ";" 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);
}
}