7 changed files with 380 additions and 2 deletions
|
After Width: | Height: | Size: 312 B |
@ -0,0 +1,20 @@ |
|||
using System.Windows; |
|||
using System.Windows.Controls; |
|||
|
|||
namespace Microsoft.Windows.Controls |
|||
{ |
|||
public class TokenItem : ContentControl |
|||
{ |
|||
static TokenItem() |
|||
{ |
|||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TokenItem), new FrameworkPropertyMetadata(typeof(TokenItem))); |
|||
} |
|||
|
|||
public static readonly DependencyProperty TokenKeyProperty = DependencyProperty.Register("TokenKey", typeof(string), typeof(TokenItem), new UIPropertyMetadata(null)); |
|||
public string TokenKey |
|||
{ |
|||
get { return (string)GetValue(TokenKeyProperty); } |
|||
set { SetValue(TokenKeyProperty, value); } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,276 @@ |
|||
//Based of the code written by Pavan Podila
|
|||
//http://blog.pixelingene.com/2010/10/tokenizing-control-convert-text-to-tokens/
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Windows; |
|||
using System.Windows.Controls; |
|||
using System.Windows.Documents; |
|||
using System.Windows.Input; |
|||
|
|||
namespace Microsoft.Windows.Controls |
|||
{ |
|||
public class TokenizedTextBox : ItemsControl |
|||
{ |
|||
#region Members
|
|||
|
|||
private System.Windows.Controls.RichTextBox _rtb = null; |
|||
private bool _surpressTextChanged = false; |
|||
private bool _surpressTextChangedEvent = false; |
|||
|
|||
#endregion //Members
|
|||
|
|||
#region Properties
|
|||
|
|||
public static readonly DependencyProperty SearchMemberPathProperty = DependencyProperty.Register("SearchMemberPath", typeof(string), typeof(TokenizedTextBox), new UIPropertyMetadata(String.Empty)); |
|||
public string SearchMemberPath |
|||
{ |
|||
get { return (string)GetValue(SearchMemberPathProperty); } |
|||
set { SetValue(SearchMemberPathProperty, value); } |
|||
} |
|||
|
|||
|
|||
public static readonly DependencyProperty TokenDelimiterProperty = DependencyProperty.Register("TokenDelimiter", typeof(string), typeof(TokenizedTextBox), new UIPropertyMetadata(";")); |
|||
public string TokenDelimiter |
|||
{ |
|||
get { return (string)GetValue(TokenDelimiterProperty); } |
|||
set { SetValue(TokenDelimiterProperty, value); } |
|||
} |
|||
|
|||
public static readonly DependencyProperty TokenTemplateProperty = DependencyProperty.Register("TokenTemplate", typeof(DataTemplate), typeof(TokenizedTextBox), new UIPropertyMetadata(null)); |
|||
public DataTemplate TokenTemplate |
|||
{ |
|||
get { return (DataTemplate)GetValue(TokenTemplateProperty); } |
|||
set { SetValue(TokenTemplateProperty, value); } |
|||
} |
|||
|
|||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TokenizedTextBox), new UIPropertyMetadata(null, OnTextChanged)); |
|||
public string Text |
|||
{ |
|||
get { return (string)GetValue(TextProperty); } |
|||
set { SetValue(TextProperty, value); } |
|||
} |
|||
|
|||
private static void OnTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) |
|||
{ |
|||
TokenizedTextBox tokenizedTextBox = o as TokenizedTextBox; |
|||
if (tokenizedTextBox != null) |
|||
tokenizedTextBox.OnTextChanged((string)e.OldValue, (string)e.NewValue); |
|||
} |
|||
|
|||
protected virtual void OnTextChanged(string oldValue, string newValue) |
|||
{ |
|||
if (_rtb == null || _surpressTextChanged) |
|||
return; |
|||
} |
|||
|
|||
public static readonly DependencyProperty ValueMemberPathProperty = DependencyProperty.Register("ValueMemberPath", typeof(string), typeof(TokenizedTextBox), new UIPropertyMetadata(String.Empty)); |
|||
public string ValueMemberPath |
|||
{ |
|||
get { return (string)GetValue(ValueMemberPathProperty); } |
|||
set { SetValue(ValueMemberPathProperty, value); } |
|||
} |
|||
|
|||
|
|||
#endregion //Properties
|
|||
|
|||
#region Constructors
|
|||
|
|||
static TokenizedTextBox() |
|||
{ |
|||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TokenizedTextBox), new FrameworkPropertyMetadata(typeof(TokenizedTextBox))); |
|||
} |
|||
|
|||
public TokenizedTextBox() |
|||
{ |
|||
CommandBindings.Add(new CommandBinding(TokenizedTextBoxCommands.Delete, DeleteToken)); |
|||
} |
|||
|
|||
#endregion //Constructors
|
|||
|
|||
#region Base Class Overrides
|
|||
|
|||
public override void OnApplyTemplate() |
|||
{ |
|||
base.OnApplyTemplate(); |
|||
|
|||
if (_rtb != null) |
|||
_rtb.TextChanged -= OnTextChanged; |
|||
_rtb = GetTemplateChild("PART_ContentHost") as System.Windows.Controls.RichTextBox; |
|||
if (_rtb != null) |
|||
_rtb.TextChanged += OnTextChanged; |
|||
|
|||
InitializeTokensFromText(); |
|||
} |
|||
|
|||
#endregion //Base Class Overrides
|
|||
|
|||
#region Event Handlers
|
|||
|
|||
private void OnTextChanged(object sender, TextChangedEventArgs e) |
|||
{ |
|||
if (_surpressTextChangedEvent) |
|||
return; |
|||
|
|||
var text = _rtb.CaretPosition.GetTextInRun(LogicalDirection.Backward); |
|||
var token = ResolveToken(text); |
|||
if (token != null) |
|||
{ |
|||
ReplaceTextWithToken(text.Trim(), token); |
|||
} |
|||
} |
|||
|
|||
#endregion //Event Handlers
|
|||
|
|||
#region Methods
|
|||
|
|||
private void InitializeTokensFromText() |
|||
{ |
|||
if (!String.IsNullOrEmpty(Text)) |
|||
{ |
|||
string[] tokens = Text.Split(new string[] { TokenDelimiter }, StringSplitOptions.RemoveEmptyEntries); |
|||
foreach (string token in tokens) |
|||
{ |
|||
var para = _rtb.CaretPosition.Paragraph; |
|||
para.Inlines.Add(CreateTokenContainer(String.Format("{0}{1}", token, TokenDelimiter), ResolveTokenFromItemsSource(token))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private object ResolveToken(string text) |
|||
{ |
|||
if (text.EndsWith(TokenDelimiter)) |
|||
return ResolveTokenFromItemsSource(text.Substring(0, text.Length - 1).Trim()); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private object ResolveTokenBySearchMemberPath(string searchText) |
|||
{ |
|||
if (ItemsSource != null) |
|||
{ |
|||
foreach (object item in ItemsSource) |
|||
{ |
|||
var property = item.GetType().GetProperty(SearchMemberPath); |
|||
if (property != null) |
|||
{ |
|||
var value = property.GetValue(item, null); |
|||
if (searchText.Equals(value.ToString(), StringComparison.InvariantCultureIgnoreCase)) |
|||
{ |
|||
return item; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return searchText; |
|||
} |
|||
|
|||
private object ResolveTokenFromItemsSource(string tokenKey) |
|||
{ |
|||
if (ItemsSource != null) |
|||
{ |
|||
foreach (object item in ItemsSource) |
|||
{ |
|||
var property = item.GetType().GetProperty(ValueMemberPath); |
|||
if (property != null) |
|||
{ |
|||
var value = property.GetValue(item, null); |
|||
if (tokenKey.Equals(value.ToString(), StringComparison.InvariantCultureIgnoreCase)) |
|||
return item; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return tokenKey; |
|||
} |
|||
|
|||
private void ReplaceTextWithToken(string inputText, object token) |
|||
{ |
|||
_surpressTextChangedEvent = true; |
|||
|
|||
var para = _rtb.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); |
|||
} |
|||
|
|||
//now append the Text
|
|||
SetTextInternal(Text + inputText); |
|||
} |
|||
|
|||
_surpressTextChangedEvent = false; |
|||
} |
|||
|
|||
private InlineUIContainer CreateTokenContainer(string tokenKey, object token) |
|||
{ |
|||
return new InlineUIContainer(CreateTokenItem(tokenKey, token)) { BaselineAlignment = BaselineAlignment.Center }; |
|||
} |
|||
|
|||
private TokenItem CreateTokenItem(string tokenKey, object token) |
|||
{ |
|||
var tokenItem = new TokenItem(); |
|||
tokenItem.TokenKey = tokenKey; |
|||
tokenItem.Content = token; |
|||
tokenItem.ContentTemplate = TokenTemplate; |
|||
|
|||
if (TokenTemplate == null) |
|||
{ |
|||
//if no template was supplied let's try to get a value from the object using the DisplayMemberPath
|
|||
if (!String.IsNullOrEmpty(DisplayMemberPath)) |
|||
{ |
|||
var property = token.GetType().GetProperty(DisplayMemberPath); |
|||
if (property != null) |
|||
{ |
|||
var value = property.GetValue(token, null); |
|||
if (value != null) |
|||
tokenItem.Content = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return tokenItem; |
|||
} |
|||
|
|||
private void DeleteToken(object sender, ExecutedRoutedEventArgs e) |
|||
{ |
|||
var para = _rtb.CaretPosition.Paragraph; |
|||
|
|||
Inline inlineToRemove = para.Inlines.SingleOrDefault(x => ((x as InlineUIContainer).Child as TokenItem).TokenKey.Equals(e.Parameter)); |
|||
|
|||
if (inlineToRemove != null) |
|||
para.Inlines.Remove(inlineToRemove); |
|||
|
|||
//update Text to remove delimited value
|
|||
SetTextInternal(Text.Replace(e.Parameter.ToString(), "")); |
|||
} |
|||
|
|||
private void SetTextInternal(string text) |
|||
{ |
|||
_surpressTextChanged = true; |
|||
Text = text; |
|||
_surpressTextChanged = false; |
|||
} |
|||
|
|||
#endregion //Methods
|
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Windows.Input; |
|||
|
|||
namespace Microsoft.Windows.Controls |
|||
{ |
|||
public static class TokenizedTextBoxCommands |
|||
{ |
|||
|
|||
private static RoutedCommand _deleteCommand = new RoutedCommand(); |
|||
public static RoutedCommand Delete |
|||
{ |
|||
get { return _deleteCommand; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:local="clr-namespace:Microsoft.Windows.Controls" > |
|||
|
|||
<Style TargetType="{x:Type local:TokenItem}"> |
|||
<Setter Property="Background" Value="#F3F7FD" /> |
|||
<Setter Property="BorderBrush" Value="#BBD8FB" /> |
|||
<Setter Property="BorderThickness" Value="1" /> |
|||
<Setter Property="Cursor" Value="Arrow" /> |
|||
<Setter Property="Padding" Value="5,1,5,1" /> |
|||
<Setter Property="Margin" Value="1,0" /> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate TargetType="{x:Type local:TokenItem}"> |
|||
<Border Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Padding="{TemplateBinding Padding}" |
|||
Margin="{TemplateBinding Margin}"> |
|||
<StackPanel Orientation="Horizontal" Margin="1"> |
|||
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" /> |
|||
<Button Margin="3,0,0,0" Cursor="Hand" Command="local:TokenizedTextBoxCommands.Delete" CommandParameter="{TemplateBinding TokenKey}" > |
|||
<Button.Template> |
|||
<ControlTemplate TargetType="Button"> |
|||
<ContentPresenter /> |
|||
</ControlTemplate> |
|||
</Button.Template> |
|||
<Image Source="/WPFToolkit.Extended;component/TokenizedTextBox/Images/delete8.png" Width="8" Height="8" /> |
|||
</Button> |
|||
</StackPanel> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style TargetType="{x:Type local:TokenizedTextBox}" > |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate TargetType="{x:Type local:TokenizedTextBox}"> |
|||
<Border x:Name="Bd" Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}"> |
|||
<Grid> |
|||
<RichTextBox x:Name="PART_ContentHost" AcceptsReturn="False" IsDocumentEnabled="True" Padding="-3,0,0,0" /> |
|||
<!--<Popup> |
|||
<Grid Background="White"> |
|||
<ItemsPresenter /> |
|||
</Grid> |
|||
</Popup>--> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
</ResourceDictionary> |
|||
Loading…
Reference in new issue