All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

341 lines
12 KiB

//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); }
}
#region Text
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;
//TODO: when text changes update tokens
}
#endregion //Text
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 -= RichTextBox_TextChanged;
_rtb.PreviewKeyDown -= RichTextBox_PreviewKeyDown;
}
_rtb = GetTemplateChild("PART_ContentHost") as System.Windows.Controls.RichTextBox;
if (_rtb != null)
{
_rtb.TextChanged += RichTextBox_TextChanged;
_rtb.PreviewKeyDown += RichTextBox_PreviewKeyDown;
}
InitializeTokensFromText();
}
#endregion //Base Class Overrides
#region Event Handlers
private void RichTextBox_TextChanged(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);
}
}
void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
InlineUIContainer container = null;
if (e.Key == Key.Back)
{
container = _rtb.CaretPosition.GetAdjacentElement(LogicalDirection.Backward) as InlineUIContainer;
}
else if (e.Key == Key.Delete)
{
//if the selected text is a blank space, I will assume that a token item is selected.
//if a token item is selected, we need to move the caret position to the left of the element so we can grab the InlineUIContainer
if (_rtb.Selection.Text == " ")
{
TextPointer moveTo = _rtb.CaretPosition.GetNextInsertionPosition(LogicalDirection.Backward);
_rtb.CaretPosition = moveTo;
}
//the cursor is to the left of a token item
container = _rtb.CaretPosition.GetAdjacentElement(LogicalDirection.Forward) as InlineUIContainer;
}
//if the container is not null that means we have something to delete
if (container != null)
{
var token = (container as InlineUIContainer).Child as TokenItem;
if (token != null)
{
SetTextInternal(Text.Replace(token.TokenKey, ""));
}
}
}
#endregion //Event Handlers
#region Methods
private void InitializeTokensFromText()
{
if (!String.IsNullOrEmpty(Text))
{
string[] tokenKeys = Text.Split(new string[] { TokenDelimiter }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tokenKey in tokenKeys)
{
var para = _rtb.CaretPosition.Paragraph;
var token = new Token(TokenDelimiter)
{
TokenKey = tokenKey,
Item = ResolveItemByTokenKey(tokenKey)
};
para.Inlines.Add(CreateTokenContainer(token));
}
}
}
private Token ResolveToken(string text)
{
if (text.EndsWith(TokenDelimiter))
return ResolveTokenBySearchMemberPath(text.Substring(0, text.Length - 1).Trim());
return null;
}
private Token ResolveTokenBySearchMemberPath(string searchText)
{
//create a new token and default the settings to the search text
var token = new Token(TokenDelimiter)
{
TokenKey = searchText,
Item = searchText
};
if (ItemsSource != null)
{
foreach (object item in ItemsSource)
{
var searchProperty = item.GetType().GetProperty(SearchMemberPath);
if (searchProperty != null)
{
var searchValue = searchProperty.GetValue(item, null);
if (searchText.Equals(searchValue.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
var valueProperty = item.GetType().GetProperty(ValueMemberPath);
if (valueProperty != null)
token.TokenKey = valueProperty.GetValue(item, null).ToString();
token.Item = item;
break;
}
}
}
}
return token;
}
private object ResolveItemByTokenKey(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, Token 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(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 with the token key
SetTextInternal(Text + token.TokenKey);
}
_surpressTextChangedEvent = false;
}
private InlineUIContainer CreateTokenContainer(Token token)
{
return new InlineUIContainer(CreateTokenItem(token)) { BaselineAlignment = BaselineAlignment.Center };
}
private TokenItem CreateTokenItem(Token token)
{
object item = token.Item;
var tokenItem = new TokenItem()
{
TokenKey = token.TokenKey,
Content = item,
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 = item.GetType().GetProperty(DisplayMemberPath);
if (property != null)
{
var value = property.GetValue(item, 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.Where(inline => inline is InlineUIContainer && ((inline as InlineUIContainer).Child as TokenItem).TokenKey.Equals(e.Parameter)).FirstOrDefault();
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
}
}