diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/Themes/Generic.xaml b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/Themes/Generic.xaml
index 8700b78a..3bb76652 100644
--- a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/Themes/Generic.xaml
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/Themes/Generic.xaml
@@ -21,9 +21,8 @@
+
-
-
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Images/delete8.png b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Images/delete8.png
new file mode 100644
index 00000000..153a6c82
Binary files /dev/null and b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Images/delete8.png differ
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenItem.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenItem.cs
new file mode 100644
index 00000000..1e48a151
--- /dev/null
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenItem.cs
@@ -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); }
+ }
+ }
+}
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBox.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBox.cs
new file mode 100644
index 00000000..a19dfcec
--- /dev/null
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBox.cs
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBoxCommands.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBoxCommands.cs
new file mode 100644
index 00000000..89e05c5f
--- /dev/null
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Implementation/TokenizedTextBoxCommands.cs
@@ -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; }
+ }
+ }
+}
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Themes/Generic.xaml b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Themes/Generic.xaml
new file mode 100644
index 00000000..ee422d5b
--- /dev/null
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/TokenizedTextBox/Themes/Generic.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj
index c16ad8eb..85f40a74 100644
--- a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj
+++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj
@@ -159,6 +159,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -291,6 +295,9 @@
+
+
+
@@ -376,6 +383,9 @@
+
+
+