wpfdata-bindingmvvmautocompletecombobox

Autocomplete TextBox that follows MVVM pattern


I have a textbox and I want to make it an autocomplete text box. The data I am going to show when I type the textbox is from my datagrid. My datagrid has 4 columns and I can pick any column from the datagrid.

Here is my textbox

         <TextBox Margin="0,93,39,18" Grid.Column="1" HorizontalAlignment="Right" Width="325">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Style.Triggers>
                        <Trigger Property="Text" Value="">
                            <Setter Property="Background" Value="{StaticResource SearchHint}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>

DataGrid

    <DataGrid Name="Datagrid"  ItemsSource="{Binding Messages}" Margin="4,0,380,413" Grid.Row="1" AutoGenerateColumns="False"  
    IsReadOnly="True"  RowBackground="WhiteSmoke" >

I googled and most of the samples I got doesn't follow WPF/MVVM pattern. What I am about to do is bind the textblock data to the grid items.Can someone help me by giving me points on where to start.


Solution

  • There are lots of different ways to approach this question, but arguably the most prominent is the editable combobox strategy (they ask a lot of questions about this in Microsoft's WPF exam which totally IGNORE the MVVM pattern, but that's an issue for another day). I'll give an example to get you started. The first step is to code up a combobox and datagrid...

      <StackPanel>
                <ComboBox
                 IsEditable="True"
                 IsTextSearchEnabled="True"
                 IsSynchronizedWithCurrentItem="True"
                 SelectedItem="{Binding MySelectedItem, Mode=TwoWay}"
                 ItemsSource="{Binding MyItems}"
                >
                    <ComboBox.InputBindings>
                        <KeyBinding  Key="Enter" Command="{Binding NotImplementedCommand}"/>
                    </ComboBox.InputBindings>
                </ComboBox>
                    <DataGrid
                    ItemsSource="{Binding DriveList}"
                    AutoGenerateColumns="True"
                    />
            </StackPanel>
    

    And then create a ViewModel...

    public class ViewModel :INotifyPropertyChanged
    {
        public ObservableCollection<string> MyItems { get; set; }
        public ObservableCollection<DriveInfo> DriveList { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;
        public ViewModel()
        {
            MyItems = new ObservableCollection<string>();
            DriveList = new ObservableCollection<DriveInfo>();
            foreach (DriveInfo di in DriveInfo.GetDrives())
            {
                DriveList.Add(di);
            }
            DriveListCollectionChanged(null, null);
            DriveList.CollectionChanged += DriveListCollectionChanged;
    
        }
        void DriveListCollectionChanged(object sender, 
                            System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            MyItems.Clear();
            List<string> temp = new List<string>();
            foreach (DriveInfo di in DriveList)
            {// add 4 columns from the DataGrid to the auto complete source
                temp.Add(di.Name);
                temp.Add(di.DriveType.ToString());
                if (di.IsReady)
                {
                    temp.Add(di.DriveFormat);
                    temp.Add(di.TotalSize.ToString());
                    temp.Add(di.AvailableFreeSpace.ToString());
                }
            }
            foreach (string s in temp.Distinct())
            {
                MyItems.Add(s);
            }
        }
        private string _mySelectedItem;
        public string MySelectedItem
        {
            get { return _mySelectedItem; }
            set
            {
                if (value != _mySelectedItem)
                {
                    _mySelectedItem = value;
                    OnPropertyChanged("MySelectedItem");
                }
            }
        }
        private void OnPropertyChanged(string s)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(s));
            }
        }
    }
    

    This example interrupts the event where the DataGrid's source is changed and populates the combo box with MULTIPLE columns. If you were just doing a single column, the solution here would vary. There is also some binding esoterica where you can automatically bind the combobox to the data, but its didactic value is questionable given what you have asked and your intent to add multiple columns of heterogeneous strings to a combobox.

    You'll need to better optimize the event handler before deploying your app because the one shown above is only for demonstration purposes.

    To wire this up, put this (or its alternate) in your Xaml...

       <Window.Resources>
            <wpfApplication3:ViewModel  x:Key="ViewModel"/>
        </Window.Resources>
        <Grid DataContext="{StaticResource ViewModel}">
            <!-- your xaml -->
        </Grid>  
    

    The ViewModel class above is an MVVM compliant solution, and you would bind an instance of this class to the DataContext of the presenting view.