Source: AutoCompleteBox with Text 2-way binding
Ok, so basically you have a following problem:
Say you have a PartnerName property on your Task entity, but you also have a Partner property. You want a PartnerName to be set, but if there’s a Partner in your database with the same name, you also want your Partner property to be set with that particular partner through async call to database.
I could bind the Text field of the out-of-box AutoCompleteControl but it acted up on me really weird, and basically it’s unusable in this type of scenario. Basically the main problem was that highlighting an item from a dropdown would overwrite what was written in the text box, and there ware a few other side-effects.
Anyway, I made my own assembly for AutoCompleteBox, you can find it if you follow the link. There are a few key changes here:
IsTextDetachedFromCollection property basically disables that a selection in the dropdown affects the text. It gets turned on the moment the Text property gets a two-way binding. It has a possibility to get disabled by setting the backing field to false
private bool _isTextDetachedFromCollection = true; public bool IsTextDetachedFromCollection { get { var e = GetBindingExpression(TextProperty); return _isTextDetachedFromCollection && e != null && e.ParentBinding.Mode == BindingMode.TwoWay; } }
It’s main usage is here, it basically disables this function.
private void OnSelectedItemChanged(object newItem) { if (IsTextDetachedFromCollection) return; . . .
Additionaly, i added that after selecting one of the list items and closing the dropdown, text property does get updated.
protected virtual void OnDropDownClosed(RoutedPropertyChangedEventArgs<bool> e) { RoutedPropertyChangedEventHandler<bool> handler = DropDownClosed; if (handler != null) { handler(this, e); } //Added part if (IsTextDetachedFromCollection) { _isTextDetachedFromCollection = false; OnSelectedItemChanged(SelectedItem); _isTextDetachedFromCollection = true; } }
Now comes the ugly part…
I had a few strange behaviours coming up during use and in the end, what i had to do is heavily alter the following function
/// <summary> /// Updates both the text box value and underlying text dependency /// property value if and when they change. Automatically fires the /// text changed events when there is a change. /// </summary> /// <param name="value">The new string value.</param> /// <param name="userInitiated">A nullable bool value indicating whether /// the action was user initiated. In a user initiated mode, the /// underlying text dependency property is updated. In a non-user /// interaction, the text box value is updated. When user initiated is /// null, all values are updated.</param> private void UpdateTextValue(string value, bool? userInitiated) { // Update the Text dependency property var triggered = false; if ((userInitiated == null || userInitiated == true) && Text != value) { _ignoreTextPropertyChange++; Text = value; OnTextChanged(new RoutedEventArgs()); triggered = true; } // Update the TextBox's Text dependency property if ( TextBox != null && TextBox.Text != value) { _ignoreTextBoxTextPropertyChange++; TextBox.Text = value ?? string.Empty; // Text dependency property value was set, fire event if ((Text == value || Text == null) && !triggered) { OnTextChanged(new RoutedEventArgs()); } } }
It’s basically about proper synchronization between a AutoCompletBox.Text property and the underlying TextBox.Text property. Very complicated stuff but i finally sorted it out.
But that’s not all, i also had to modify the following method
/// <summary> /// Handle the update of the text for the control from any source, /// including the TextBox part and the Text dependency property. /// </summary> /// <param name="newText">The new text.</param> /// <param name="userInitiated">A value indicating whether the update /// is a user-initiated action. This should be a True value when the /// TextUpdated method is called from a TextBox event handler.</param> private void TextUpdated(string newText, bool userInitiated) { // Only process this event if it is coming from someone outside // setting the Text dependency property directly. if (_ignoreTextBoxTextPropertyChange > 0 && userInitiated) { _ignoreTextBoxTextPropertyChange--; //hotfix sa if-om dogadja se situacija da pri pejstanju nove vrijednosti na prazno polje nema sadrzaja if (!string.IsNullOrEmpty(Text) || string.IsNullOrEmpty(newText)) return; } // Only process this event if it is coming from someone outside // setting the Text dependency property directly. if (_ignoreTextPropertyChange > 0 && !userInitiated) { _ignoreTextPropertyChange--; return; }
This method gets called by the previous one, and i had to redo some flags because of asynchronous call to routed events. Messy stuff i tell ya!
Though i think the latter changes definitely SHOULD go into the autocompletebox generally, it does the same thing as a previous solution but it’s more robust.
In the end i disabled TextCompletion in when using Text binding:
private void UpdateTextCompletion(bool userInitiated) { //Added if (IsTextDetachedFromCollection) return; . . .
So i guess that’s about it! It’s a few changes here and there, but now i have it working pretty straightforward and clean. Oh, and there’s an added bonus of trimming the input if you need it (it’s in the code in few places, you’ll have no problems finding it. If you don’t then just disable it. Happy coding!
Mictian Jan 17 , 2012 at 7:04 pm /
Hi man!, I’d like to contact you, I want to know if you could provide an example of your dataform, I’m looking for some features that maybe you already have implemented.
Anyway a “Contact me” would be gr8.
thx in advance!
Bruno Jan 29 , 2012 at 1:15 pm /
Hey there!
My contact info is on the about page, so you can use any one of those means, i’ll be glad to help