KrystianB
KrystianB

Reputation: 502

C# WPF Combobox editable only allow option from list

I have a combobox with names in it. I have the box set to editable so that the user can enter a name. I want it so that the user can only enter a name that is already in the list. When the user clicks save I want the box to have the red validation border show up if the box is empty or not in the list. Is there a way to do this?

        <ComboBox IsEditable="True"
                  Grid.Column="2"
                  Grid.Row="1"
                  Margin="5,3,0,0"
                  Text="{Binding Model.Number}"
                  ItemsSource="{Binding DList}"
                  SelectedItem="{Binding Model.Number}"
                  IsEnabled="{Binding EnableComboBox}" 
                  VerticalAlignment="Top">
        </ComboBox>

Upvotes: 8

Views: 6098

Answers (2)

user6996876
user6996876

Reputation:

Let's assume you use MVVM (it's not what you're doing now) and that

ItemsSource="{Binding DList}"

is a correct binding to a collection of Models

You'd need a

DisplayMemberPath="Number"

Back to your question. First, let's write another binding for the selected Text

Text="{Binding Selected, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors =true, NotifyOnValidationError=true}"

and implement the validation tooltip inside the combo

ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"

and the style in the window resources

<Window.Resources>
    <Style TargetType="{x:Type Label}">
        <Setter Property="Margin" Value="5,0,5,0" />
        <Setter Property="HorizontalAlignment" Value="Right" />
    </Style>
    <Style TargetType="{x:Type ComboBox}">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="Margin" Value="0,2,40,2" />
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel LastChildFill="true">
                        <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                                ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                            <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                            </TextBlock>
                        </Border>
                        <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                            <Border BorderBrush="red" BorderThickness="1" />
                        </AdornedElementPlaceholder>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Finally, we've to validate the ViewModel property

We can project the model list to its numbers for the error check

    public class VM : IDataErrorInfo
    {
        public string this[string columnName]
        {
            get
            {
                if (columnName.Equals( "Selected"))
                {
                    if (!DList.Select(m => m.Number).Contains(Selected))
                        return "Selected number must be in the combo list";
                }
                return null;
            }
        }

You can learn more about data validation in MVVM for example here

Fire the validation later

Say you want fire the validation after a save button is clicked

    <Button Content="Save" 
            Command="{Binding SaveCmd}"

you simply need to raise the property changed in the corresponding delegate command

    public class VM : ViewModelBase, IDataErrorInfo
    {
        private bool showValidation;
        private int selected;
        public int Selected
        {
            get { return selected; }
            set
            {
                selected = value;
                showValidation = true;
                OnPropertyChanged("Selected");
            }
        }
        DelegateCommand saveCmd;
        public ICommand SaveCmd
        {
            get
            {
                if (saveCmd == null)
                {
                    saveCmd = new DelegateCommand(_ => RunSaveCmd(), _ => CanSaveCmd());
                }
                return saveCmd;
            }
        }

        private bool CanSaveCmd()
        {
            return true;
        }

        private void RunSaveCmd()
        {
            showValidation = true;
            OnPropertyChanged("Selected");
        }

and exit from the validation before you want to show it.

        public string this[string columnName]
        {
            get
            {
                if (!showValidation)
                {
                    return null;
                }

Upvotes: 4

Sami
Sami

Reputation: 2110

If I understood correctly, you want the user to be able to select an existing list item by typing, but not type a string that is not on the list. That can be done with the following:

<ComboBox IsEditable="False"></ComboBox>

This will allow the user to start typing the string, but you lose the textbox for input.

Another way to do it is to allow the user to type whatever they want by setting <ComboBox IsReadOnly="False" IsEditable="True"> and handle for example the LostFocus event to check if the input is valid. Here's an example:

private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
    {
        bool allowed = false;
        foreach (ComboBoxItem it in comboBox.Items)
        {
            if (it.Content.ToString() == comboBox.Text)
            {
                allowed = true;
                break;
            }
        }

        if (!allowed)
        {
            MessageBox.Show("MISS!");
        }

        else
        {
            MessageBox.Show("HIT!");
        }
    }

For some reason I wasn't able to set the border color quickly, but you get the point from here. Also depending on your ComboBoxItem type, you may need to match the comboBox.Text to a certain property.

Upvotes: 5

Related Questions