AutoCompleteBox with Attached Command to handle TextChanged Event (Issue)

Aug 11, 2010 at 10:40 PM


**Already posted on the prism discussion forums **

 

Hi all I created a attached behavior command to handle the textchanged event for the Autocompletebox control.  Alot of people have been interested in this since it seems quite silly that the default Autocomplete control does not facilitate the Re-binding of its itemsource when the text is changed.  That in my mind is a normal requirement for LOB apps since the defualt control expects you to return the entire list of data and in my and most LOB cases it could be hundreds of thousands of records.  Anyways here is the code for the attached behavior as well as the Xaml for control. 

*HELP*One strange issue i have noticed however is that when you have more than one item in your dropdown list, when you click on one instead of selecting the item you picked it replaces the value with the prefix text.  I.e I selected Minneapolis from the list and the Value displayed is M ?!  I can't figure this out, what i did notice is what happens is the selectedItem property (in mycase selectedcity) is re-fired when i re-bind the list since i guess just selected an item from the list is considered a Textchanged event.  I can think of some sordid and unscruptilous work arounds but I am reather new to PRISIM so if anyone else has any ideas on this bug I would be apreciative.  I do have a work around but its pretty bad.

Other than that Enjoy !  ( PS it took me a week to figure out how to do this , most code samples are in C# and again I am new to MVVM as well as prisim so bear with me on bad practices and stuff )

 

Attached behavior code :

 

Imports System.Windows.Controls
Imports System.Windows.Input
Imports Microsoft.Practices.Composite.Presentation.Commands
Imports System.Windows




Public NotInheritable Class TextChanged
Private Sub New()

End Sub



Private Shared ReadOnly BehaviorNameCommandBehaviorProperty As DependencyProperty = DependencyProperty.RegisterAttached("TextChangedBehavior", GetType(AutoCompleteBoxTextChangedCommandBehavior), GetType(TextChanged), Nothing)

Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(ICommand), GetType(TextChanged), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnSetCommandCallback)))

' Public Shared ReadOnly CommandProperty2 As DependencyProperty = DependencyProperty.Register("Command", GetType(ICommand), GetType(CommandReference), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnCommandChanged)))


Public Shared ReadOnly CommandParameterProperty As DependencyProperty = DependencyProperty.RegisterAttached("CommandParameter", GetType(Object), GetType(TexTChanged), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnSetCommandParameterCallback)))

Public Shared Function GetCommand(ByVal control As AutoCompleteBox) As ICommand
Return TryCast(control.GetValue(CommandProperty), ICommand)
End Function

Public Shared Sub SetCommand(ByVal control As AutoCompleteBox, ByVal command As ICommand)
control.SetValue(CommandProperty, command)
End Sub

Public Shared Sub SetCommandParameter(ByVal control As AutoCompleteBox, ByVal parameter As Object)
control.SetValue(CommandParameterProperty, parameter)
End Sub

Public Shared Function GetCommandParameter(ByVal control As AutoCompleteBox) As Object
Return control.GetValue(CommandParameterProperty)
End Function

Private Shared Sub OnSetCommandCallback(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim control As AutoCompleteBox = TryCast(dependencyObject, AutoCompleteBox)
If control IsNot Nothing Then
Dim behavior As AutoCompleteBoxTextChangedCommandBehavior = GetOrCreateBehavior(control)
behavior.Command = TryCast(e.NewValue, ICommand)
End If
End Sub

Private Shared Sub OnSetCommandParameterCallback(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim control As AutoCompleteBox = TryCast(dependencyObject, AutoCompleteBox)
If control IsNot Nothing Then
Dim behavior As AutoCompleteBoxTextChangedCommandBehavior = GetOrCreateBehavior(control)
behavior.CommandParameter = e.NewValue
End If
End Sub

Private Shared Function GetOrCreateBehavior(ByVal control As AutoCompleteBox) As AutoCompleteBoxTextChangedCommandBehavior
Dim behavior As AutoCompleteBoxTextChangedCommandBehavior = TryCast(control.GetValue(BehaviorNameCommandBehaviorProperty), AutoCompleteBoxTextChangedCommandBehavior)
If behavior Is Nothing Then
behavior = New AutoCompleteBoxTextChangedCommandBehavior(control)
control.SetValue(BehaviorNameCommandBehaviorProperty, behavior)
End If
Return behavior
End Function
End Class

Public Class AutoCompleteBoxTextChangedCommandBehavior
Inherits CommandBehaviorBase(Of AutoCompleteBox)
Public Sub New(ByVal control As AutoCompleteBox)
MyBase.New(control)
AddHandler control.TextChanged, AddressOf OnEventName
End Sub

Private Sub OnEventName(ByVal sender As Object, ByVal e As RoutedEventArgs)
ExecuteCommand()
End Sub
End Class

 

Here is the XAML code for my view complete with my namespace declarations :  My namespace for is Inferstrcuture. 

 

 xmlns:infastructure="clr-namespace:Infastructure;assembly=Infastructure" 

 

 

<sdk:AutoCompleteBox  Grid.Row="4" Grid.Column="2" Height="23" Grid.ColumnSpan="2"    Margin="45,0,0,0"                            
Name="AutoCompleteBox1" Width="120"
FilterMode="StartsWith"
IsTextCompletionEnabled="True"
MinimumPrefixLength="0" MinimumPopulateDelay="100"
MaxDropDownHeight="150"
ItemsSource="{Binding Path=CityList}" SelectedItem="{Binding Path=SelectedCity}"
Text="{Binding Path=SelectedCity, Mode=TwoWay,ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
ValueMemberBinding="{Binding City}"
infastructure:TextChanged.Command=
"{Binding Command1}"
infastructure:TextChanged.CommandParameter="{Binding Path=SelectedCity}"
>
<sdk:AutoCompleteBox.ItemTemplate>
<DataTemplate >
<StackPanel >
<TextBlock Text="{Binding Path=City}" />
</StackPanel>
</DataTemplate>
</sdk:AutoCompleteBox.ItemTemplate>
</sdk:AutoCompleteBox>


Here is my ViewModel Code (Just the relevant bits) p.s THe parameter passed is the prefix text to search the database for the city with
   ' Initialize this ViewModel's command.
Command1 = New DelegateCommand(Of String)(AddressOf ExecuteCommand1, AddressOf CanExecuteCommand1)



#Region "Command1"
Public Property Command1() As DelegateCommand(Of String)
Get
Return m_command1
End Get
Private Set(ByVal value As DelegateCommand(Of String))
m_command1 = value
End Set
End Property
Private m_command1 As DelegateCommand(Of String)


Private Sub ExecuteCommand1(ByVal commandParameter As String)
When something is selected, the value is used to populate the second combobox
Dim strCountryName As String = Replace(SelectedCountry, " ", "") 'trim out any spaces if needed
'populates second combobox i.e citys
'load the cities
RaisePropertyChanged("CityList")
'clear previous list of cities from the data context
_newPostalDataService.CityLists.Clear()
'load the new list of cities
Dim loadOperationCityList As LoadOperation(Of Dating.Server.Data.CityList) = _newPostalDataService.Load(Of Dating.Server.Data.CityList) _
(_newPostalDataService.GetCityListDynamicQuery(strCountryName, commandParameter, ""))
AddHandler loadOperationCityList.Completed, AddressOf CityLoadisCompleted

End Sub

Private Function CanExecuteCommand1(ByVal commandParameter As String) As Boolean
' Command is only enabled when there is a country selected
Return True
End Function
#End Region

Private Sub CityLoadisCompleted(ByVal sender As Object, ByVal e As EventArgs)
Dim loadOperation = DirectCast(sender, LoadOperation)

If loadOperation.IsComplete AndAlso Not loadOperation.IsCanceled AndAlso Not loadOperation.HasError Then

CityList() = New ObservableCollection(Of Dating.Server.Data.CityList)(_newPostalDataService.CityLists)

RaisePropertyChanged("CityList")


Else 'this will be handled to the server as a database or log file error in prod
System.Windows.MessageBox.Show(loadOperation.Error.ToString, "Load Error", System.Windows.MessageBoxButton.OK)
loadOperation.MarkErrorAsHandled()
End If
End Sub

Private m_CityList As New System.Collections.ObjectModel.ObservableCollection(Of Dating.Server.Data.CityList)
Public Property CityList() As System.Collections.ObjectModel.ObservableCollection(Of Dating.Server.Data.CityList)

Get
Return m_CityList
End Get

Set(ByVal value As System.Collections.ObjectModel.ObservableCollection(Of Dating.Server.Data.CityList))
m_CityList = value
RaisePropertyChanged("CityList")

End Set
End Property


Private Sub SelectedItemChanged(ByVal sender As Object, ByVal e As EventArgs)
' Update the command status.
Command1.RaiseCanExecuteChanged()
End Sub


'Selection, Seccond COmbobox
Private m_SelectedCity As String
Public Property SelectedCity() As String
Get

Return m_SelectedCity

End Get
Set(ByVal value As String)
m_SelectedCity = value
RaisePropertyChanged("SelectedCity")
End Set
End Property