AutoCompleteBox with Attached Command to handle TextChanged Event (Issue)

Topics: Prism v2 - Silverlight 2, Prism v2 - Silverlight 3, Prism v2 - Silverlight 4, Prism v2 - WPF 4, Prism v4 - Silverlight 4
Aug 11, 2010 at 10:18 PM

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

 

 

 

                     
                    
                        
                    
                        
                    
                                  
                      
           

 

 

Aug 11, 2010 at 10:25 PM

You may want to see if the folks who run the Prism Contrib project (http://compositewpfcontrib.codeplex.com/) are interested in adding this to the community supported extensions to Prism.

Thanks,
Michael Puleio

Aug 11, 2010 at 10:40 PM

sure thing