Can a WPF ComboBox display alternative text when its selection is null?
G'day!
I want my WPF ComboBox
to display some alternative text when its data-bound selection is null
.
The view model has the expected properties:
public ThingoSelectionViewModel : INotifyPropertyChanged {
public ThingoSelectionViewModel(IProvideThingos) {
this.Thingos = IProvideThingos.GetThingos();
}
public ObservableCollection<Thingo> Thingos { get; set; }
public Thingo SelectedThingo {
get { return this.selectedThingo; }
set { // set this.selectedThingo and raise the property change notification
}
// ...
}
The view has XAML binding to the view model in the expected way:
<ComboBox x:Name="ComboboxDrive" SelectedItem="{Binding Path=SelectedThingo}"
IsEditable="false" HorizontalAlignment="Left" MinWidth="100"
IsReadOnly="false" Style="{StaticResource ComboboxStyle}"
Grid.Column="1" Grid.Row="1" Margin="5" SelectedIndex="0">
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem IsEnabled="False">Select a thingo</ComboBoxItem>
<CollectionContainer
Collection="{Binding Source={StaticResource Thingos}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
The ComboBoxItem
wedged into the top is a way to get an extra item at the top. It's pure chrome: the view model stays pure and simple. There's just one problem: the users want "Select a thingo" displayed whenever the ComboBox' selection is null.
The users do not want a thingo selected by default. They want to see a message telling them to select a thingo.
I'd like to avoid having to pollute the viewmodel with a ThingoWrapper
class with a ToString
method returning "Select a thingo" if its .ActualThingo
property is null, wrapping each Thingo
as I populate Thingos
, and figuring out some way to prevent the user from selecti开发者_JAVA技巧ng the nulled Thingo
.
Is there a way to display "Select a thingo" within the ComboBox
' boundaries using pure XAML, or pure XAML and a few lines of code in the view's code-behind class?
How strict is your MVVM requirement? Can you have a little code-behind in the view?
Perhaps you could contain the ComboBox in a grid, something like this:
<Grid>
<ComboBox x:Name="ComboBoxControl"
SelectionChanged="ComboBoxControl_SelectionChanged"
HorizontalAlignment="Left" VerticalAlignment="Top"
MinWidth="{Binding ElementName=UnselectedText, Path=ActualWidth}">
<ComboBoxItem>One</ComboBoxItem>
<ComboBoxItem>Two</ComboBoxItem>
<ComboBoxItem>Three</ComboBoxItem>
</ComboBox>
<TextBlock IsHitTestVisible="False"
x:Name="UnselectedText"
HorizontalAlignment="Left"
Text="Select an option..."
VerticalAlignment="Top" Margin="4"
Padding="0,0,30,0" />
</Grid>
Then, in the code-behind, insert some logic in an event handler:
Private Sub ComboBoxControl_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
If ComboBoxControl.SelectedIndex = -1 Then
UnselectedText.Visibility = Windows.Visibility.Visible
Else
UnselectedText.Visibility = Windows.Visibility.Hidden
End If
End Sub
Setting the IsHitTestVisible="False"
DependencyProperty on the TextBlock lets mouse events through so that you can click on the ComboBox, and setting the visibility to Hidden
in the code-behind keeps the layout of a default ComboBox's appearance from jumping around when the prompt text is hidden.
You can't use a control template trigger, but you could set up a simple item template for the combobox:
<ComboBox ItemsSource="{Binding}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="displayText" Text="{Binding}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter TargetName="displayText" Property="Text" Value="Default Value" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Edit: Looks like the trigger idea is a no go. I added the following to the control template of a test combo box to no avail:
<Trigger Property="SelectedItem" Value="{x:Null}">
<Setter Property="Text" Value="No Item Selected"/>
</Trigger>
Additionally, when trying to edit the control template in Blend (Edit Current) I am left with a featureless combobox, no colors, just an ugly button (but there is a borderless dropdown). Try someone elses suggestion (Mike Brown perhaps).
Original:
You can use a Trigger in the Control template. Here is an example using a ListBox from an app I am working on.
<ControlTemplate x:Key="SnazzyFormListBoxTemplate" TargetType="{x:Type ListBox}">
<Microsoft_Windows_Themes:ClassicBorderDecorator x:Name="Bd" SnapsToDevicePixels="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderStyle="Sunken" BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Padding="{TemplateBinding Padding}" Focusable="False" Template="{DynamicResource SnazzyScrollViewerControlTemplate}">
<Grid>
<TextBlock x:Name="textBlock" Text="No Items" FontFamily="Arial" FontWeight="Bold" FontSize="13.333" Foreground="#4D000000" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,10"/>
<ItemsPresenter x:Name="itemsPresenter" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Microsoft_Windows_Themes:ClassicBorderDecorator>
<ControlTemplate.Triggers>
<Trigger Property="Selector.IsSelected" Value="True"/>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" TargetName="textBlock" Value="Visible"/>
<Setter Property="Visibility" TargetName="itemsPresenter" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="True">
<Setter Property="Visibility" TargetName="textBlock" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="itemsPresenter" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
The above ControlTemplate has a Trigger which checks the Property HasItems. If False, a textblock saying "No Items" is displayed in the middle of the ListBox. If there are Items, they are displayed.
In your case change the trigger to check to see if ItemSelected is x:Null and set the Text property to "Nothing Selected".
The path of least resistance here that I've found is to use the Null Object Pattern For an example of using this pattern in the .NET Framework, consider the static value Double.NaN if you create a Null Object for your Thingo, in your view model you can append it to the front of your list to signify "nothing is selected". Create a DataTemplate for the Thingo class that has a DataTrigger for the Null Object instance that shows "Select a Value".
I could give a code sample but it's past my bed time.
I know this is an old thread, but here is how I do it. After I fetch the Thingos collection, I simply insert a new Thingo with a bogus ID value and a display value of "Select a thingo."
public ThingoSelectionViewModel(IProvideThingos) {
this.Thingos = IProvideThingos.GetThingos();
Thingo newThingo = new Thingo();
newThingo.ThingoID = -1;
newThingo.ThingoDisplayName = "Select a thingo";
this.Thingos.Insert(0, newThingo);
}
Now, when the ComboBox is databound, the first item is "Select a thingo." Then when a Thingo is selected, I test the ID of the SelectedThingo, and act on it accordingly.
I know I'm resurrecting an old post, but this was the first one that came up on my google search. In Visual Studio, you can choose to set the Default Selection to 0, instead of -1, and just have your first selection be the default text.
<ComboBox x:name="ThingoSelector" SelectedIndex="0">
<ComboBoxItem IsEnabled="False">Choose Thingo</ComboBoxItem>
<ComboBoxItem>Thingo 1</ComboBoxItem>
</ComboBox>
Another option:
<ComboBox>
<ComboBoxItem Visibility="Collapsed" IsSelected="True">
<TextBlock Text="Choose item" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="Item 1" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="Item 2" />
</ComboBoxItem>
</ComboBox>
精彩评论