Can a WPF ComboBox display alternative text when its selection is null?
Asked Answered
K

7

10

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 selecting 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?

Kalidasa answered 25/5, 2010 at 2:4 Comment(1)
FWIW: I ended up implementing the ThingoWrapper, modifying the ThingoSelectionViewModel to cope with selection of the wrapped null value, and finding ways to automatically select the Whatsy and Fadoozamy objects so I didn't have to wrap them, also.Kalidasa
M
3

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.

Moisten answered 25/5, 2010 at 4:40 Comment(0)
H
9

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.

Herminiahermione answered 29/6, 2010 at 6:37 Comment(1)
Even if you want to do it that way you don't have to break your MVVM. Just use a converter to convert the selection to visibility.Rubble
J
5

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>
Jerkwater answered 25/5, 2010 at 15:42 Comment(1)
Hmm. I'll have to check this one out, too.Kalidasa
M
4

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".

Menopause answered 25/5, 2010 at 2:34 Comment(3)
User is asking how to have a "nothing selected" value in the combobox, not to have a different look for an empty combobox.Moisten
I know. They can set a trigger in the control template that checks the state of Items selected and if it is null then set the value of the text of the box to "Nothing Selected". I was hoping the user could infer that from the example given. Updated my answer to better fit.Menopause
@Mike Brown: I have edited my answer, so I would appreciate it if you could remote the downvote.Menopause
M
3

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.

Moisten answered 25/5, 2010 at 4:40 Comment(0)
C
1

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.

Coble answered 29/6, 2011 at 20:43 Comment(2)
That's basically the Null Object Pattern. I sometimes use a static property of the class to expose the Null Object so people can check their thingo against Thingo.NotSpecified.Kalidasa
Figures.... I'm often asked if I do or use "such and such" a methodology or process, and I think the answer is 'No.' Only to find out that the answer is 'Yes,' but I simply didn't know what it was officially called. Thanks.Biathlon
A
0

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>
Atalie answered 23/1, 2016 at 22:58 Comment(0)
J
0

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>
Joggle answered 10/1, 2018 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.