Reputation: 502
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
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
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
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