117
votes

In a WPF app, in MVP app, I have a combo box,for which I display the data fetched from Database. Before the items added to the Combo box, I want to display the default text such as

" -- Select Team --"

so that on pageload it displays and on selecting it the text should be cleared and the items should be displayed.

Selecting data from DB is happening. I need to display the default text until the user selects an item from combo box.

Please guide me

23

23 Answers

113
votes

The easiest way I've found to do this is:

<ComboBox Name="MyComboBox"
 IsEditable="True"
 IsReadOnly="True"
 Text="-- Select Team --" />

You'll obviously need to add your other options, but this is probably the simplest way to do it.

There is however one downside to this method which is while the text inside your combo box will not be editable, it is still selectable. However, given the poor quality and complexity of every alternative I've found to date, this is probably the best option out there.

92
votes

You can do this without any code behind by using a IValueConverter.

<Grid>
   <ComboBox
       x:Name="comboBox1"
       ItemsSource="{Binding MyItemSource}"  />
   <TextBlock
       Visibility="{Binding SelectedItem, ElementName=comboBox1, Converter={StaticResource NullToVisibilityConverter}}"
       IsHitTestVisible="False"
       Text="... Select Team ..." />
</Grid>

Here you have the converter class that you can re-use.

public class NullToVisibilityConverter : IValueConverter
{
    #region Implementation of IValueConverter

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value == null ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

And finally, you need to declare your converter in a resource section.

<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />

Where Converters is the place you have placed the converter class. An example is:

xmlns:Converters="clr-namespace:MyProject.Resources.Converters"

The very nice thing about this approach is no repetition of code in your code behind.

50
votes

I like Tri Q's answer, but those value converters are a pain to use. PaulB did it with an event handler, but that's also unnecessary. Here's a pure XAML solution:

<ContentControl Content="{Binding YourChoices}">
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <Grid>
                <ComboBox x:Name="cb" ItemsSource="{Binding}"/>
                <TextBlock x:Name="tb" Text="Select Something" IsHitTestVisible="False" Visibility="Hidden"/>
            </Grid>
            <DataTemplate.Triggers>
                <Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
                    <Setter TargetName="tb" Property="Visibility" Value="Visible"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ContentControl.ContentTemplate> 
</ContentControl>
35
votes

No one said a pure xaml solution has to be complicated. Here's a simple one, with 1 data trigger on the text box. Margin and position as desired

<Grid>
    <ComboBox x:Name="mybox" ItemsSource="{Binding}"/>
    <TextBlock Text="Select Something" IsHitTestVisible="False">
           <TextBlock.Style>
                <Style TargetType="TextBlock">
                      <Setter Property="Visibility" Value="Hidden"/>
                      <Style.Triggers>
                            <DataTrigger Binding="{Binding ElementName=mybox,Path=SelectedItem}" Value="{x:Null}">
                                  <Setter Property="Visibility" Value="Visible"/>
                             </DataTrigger>
                      </Style.Triggers>
                </Style>
           </TextBlock.Style>
     </TextBlock>
</Grid>
26
votes

Set IsEditable="True" on the ComboBox element. This will display the Text property of the ComboBox.

16
votes

I dont know if it's directly supported but you could overlay the combo with a label and set it to hidden if the selection isn't null.

eg.

<Grid>
   <ComboBox Text="Test" Height="23" SelectionChanged="comboBox1_SelectionChanged" Name="comboBox1" VerticalAlignment="Top" ItemsSource="{Binding Source=ABCD}"  />
   <TextBlock IsHitTestVisible="False" Margin="10,5,0,0" Name="txtSelectTeam" Foreground="Gray" Text="Select Team ..."></TextBlock>
</Grid>

Then in the selection changed handler ...

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    txtSelectTeam.Visibility = comboBox1.SelectedItem == null ? Visibility.Visible : Visibility.Hidden;
}
7
votes

Based on IceForge's answer I prepared a reusable solution:

xaml style:

<Style x:Key="ComboBoxSelectOverlay" TargetType="TextBlock">
    <Setter Property="Grid.ZIndex" Value="10"/>
    <Setter Property="Foreground" Value="{x:Static SystemColors.GrayTextBrush}"/>
    <Setter Property="Margin" Value="6,4,10,0"/>
    <Setter Property="IsHitTestVisible" Value="False"/>
    <Setter Property="Visibility" Value="Hidden"/>
    <Style.Triggers>
        <DataTrigger Binding="{Binding}" Value="{x:Null}">
            <Setter Property="Visibility" Value="Visible"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

example of use:

<Grid>
     <ComboBox x:Name="cmb"
               ItemsSource="{Binding Teams}" 
               SelectedItem="{Binding SelectedTeam}"/>
     <TextBlock DataContext="{Binding ElementName=cmb,Path=SelectedItem}"
               Text=" -- Select Team --" 
               Style="{StaticResource ComboBoxSelectOverlay}"/>
</Grid>
4
votes

Not tried it with combo boxes but this has worked for me with other controls...

ageektrapped blogpost

He uses the adorner layer here to display a watermark.

3
votes

HappyNomad's solution was very good and helped me eventually arrive at this slightly different solution.

<ComboBox x:Name="ComboBoxUploadProject" 
    Grid.Row="2"
    Width="200" 
    Height="23"                           
    Margin="64,0,0,0"
    ItemsSource="{Binding projectList}"
    SelectedValue ="{Binding projectSelect}" 
    DisplayMemberPath="projectName"
    SelectedValuePath="projectId"
    >
    <ComboBox.Template>
        <ControlTemplate TargetType="ComboBox">
            <Grid>
                <ComboBox x:Name="cb" 
                    DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" 
                    ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
                    SelectedValue ="{Binding SelectedValue, RelativeSource={RelativeSource TemplatedParent}}" 
                    DisplayMemberPath="projectName"
                    SelectedValuePath="projectId"
                    />
                <TextBlock x:Name="tb" Text="Select Item..." Margin="3,3,0,0" IsHitTestVisible="False" Visibility="Hidden"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
                    <Setter TargetName="tb" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </ComboBox.Template>
</ComboBox>
2
votes

Easiest way is to use CompositeCollection to merge default text and data from database directly in ComboBox e.g.

    <ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
        <ComboBox.ItemsSource>
            <CompositeCollection>
                <ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
                <CollectionContainer Collection="{Binding Source={StaticResource ResourceKey=MyComboOptions}}"/>
            </CompositeCollection>
        </ComboBox.ItemsSource>
    </ComboBox>

And in Resources define StaticResource to bind ComboBox options to your DataContext, because direct binding in CollectionContainer doesn't work correctly.

<Window.Resources>
    <CollectionViewSource Source="{Binding}" x:Key="MyComboOptions" />
</Window.Resources>

This way you can define your ComboBox options only in xaml e.g.

   <ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
        <ComboBox.ItemsSource>
            <CompositeCollection>
                <ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
                <ComboBoxItem >Option 1</ComboBoxItem>
                <ComboBoxItem >Option 2</ComboBoxItem>
            </CompositeCollection>
        </ComboBox.ItemsSource>
    </ComboBox>
1
votes

I would recommend the following:

Define a behavior

public static class ComboBoxBehaviors
{
    public static readonly DependencyProperty DefaultTextProperty =
        DependencyProperty.RegisterAttached("DefaultText", typeof(String), typeof(ComboBox), new PropertyMetadata(null));

    public static String GetDefaultText(DependencyObject obj)
    {
        return (String)obj.GetValue(DefaultTextProperty);
    }

    public static void SetDefaultText(DependencyObject obj, String value)
    {
        var combo = (ComboBox)obj;

        RefreshDefaultText(combo, value);

        combo.SelectionChanged += (sender, _) => RefreshDefaultText((ComboBox)sender, GetDefaultText((ComboBox)sender));

        obj.SetValue(DefaultTextProperty, value);
    }

    static void RefreshDefaultText(ComboBox combo, string text)
    {
        // if item is selected and DefaultText is set
        if (combo.SelectedIndex == -1 && !String.IsNullOrEmpty(text))
        {
            // Show DefaultText
            var visual = new TextBlock()
            {
                FontStyle = FontStyles.Italic,
                Text = text,
                Foreground = Brushes.Gray
            };

            combo.Background = new VisualBrush(visual)
            {
                Stretch = Stretch.None,
                AlignmentX = AlignmentX.Left,
                AlignmentY = AlignmentY.Center,
                Transform = new TranslateTransform(3, 0)
            };
        }
        else
        {
            // Hide DefaultText
            combo.Background = null;
        }
    }
}

User the behavior

<ComboBox Name="cmb" Margin="72,121,0,0" VerticalAlignment="Top"
          local:ComboBoxBehaviors.DefaultText="-- Select Team --"/>
1
votes

IceForge's answer was pretty close, and is AFAIK the easiest solution to this problem. But it missed something, as it wasn't working (at least for me, it never actually displays the text).

In the end, you can't just set the "Visibility" property of the TextBlock to "Hidden" in order for it to be hidden when the combo box's selected item isn't null; you have to SET it that way by default (since you can't check not null in triggers, by using a Setter in XAML at the same place as the Triggers.

Here's the actual solution based on his, the missing Setter being placed just before the Triggers:

<ComboBox x:Name="combo"/>
<TextBlock Text="--Select Team--" IsHitTestVisible="False">
    <TextBlock.Style>
        <Style TargetType="TextBlock">

            <Style.Setters>
                <Setter Property="Visibility" Value="Hidden"/>
            </Style.Setters>

            <Style.Triggers>
                <DataTrigger Binding="{Binding ElementName=combo,Path=SelectedItem}" Value="{x:Null}">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBlock>
0
votes

Not best practice..but works fine...

<ComboBox GotFocus="Focused"  x:Name="combobox1" HorizontalAlignment="Left" Margin="8,29,0,0" VerticalAlignment="Top" Width="128" Height="117"/>

Code behind

public partial class MainWindow : Window
{
    bool clearonce = true;
    bool fillonce = true;
    public MainWindow()
    {
        this.InitializeComponent();          
        combobox1.Items.Insert(0, " -- Select Team --");
        combobox1.SelectedIndex = 0;
    }

    private void Focused(object sender, RoutedEventArgs e)
    {
            if(clearonce)
            {
                combobox1.Items.Clear();
                clearonce = false;
            }
            if (fillonce)
            {
              //fill the combobox items here 
                for (int i = 0; i < 10; i++)
                {
                    combobox1.Items.Insert(i, i);
                }
                fillonce = false;
            }           
    }
}
0
votes

I believe a watermark as mentioned in this post would work well in this case

There's a bit of code needed but you can reuse it for any combobox or textbox (and even passwordboxes) so I prefer this way

0
votes

I am using an IsNullConverter class in my project and it worked for me. here is the code for it in c#,create a folder named Converter and add this class in that folder,as the trigger used doesnt supports value for rather than null,and IsNullConverter just do that

 public class IsNullConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value == null);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
    }
}

add the namespace in xaml file like this.

xmlns:Converters="clr-namespace:TymeSheet.Converter"

means

xmlns:Converters="clr-namespace:YourProjectName.Converter"

use this line below the resources to make it availabe through xaml code

<Converters:IsNullConverter x:Key="isNullConverter" />

here is the xaml code,i used here the trigger so whenever an item is selected in the combobox the visibilty of your text becomes false.

<TextBlock Text="Select Project" IsHitTestVisible="False" FontFamily="/TimeSheet;component/Resources/#Open Sans" FontSize="14" Canvas.Right="191" Canvas.Top="22">
                        <TextBlock.Resources>
                            <Converters:IsNullConverter x:Key="isNullConverter"/>
                        </TextBlock.Resources>
                        <TextBlock.Style>
                            <Style TargetType="TextBlock">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding ElementName=ProjectComboBox,Path=SelectedItem,Converter={StaticResource isNullConverter}}" Value="False">
                                        <Setter Property="Visibility" Value="Hidden"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </TextBlock.Style>
                    </TextBlock>
0
votes

//XAML Code

// ViewModel code

    private CategoryModel _SelectedCategory;
    public CategoryModel SelectedCategory
    {
        get { return _SelectedCategory; }
        set
        {
            _SelectedCategory = value;
            OnPropertyChanged("SelectedCategory");
        }
    }

    private ObservableCollection<CategoryModel> _Categories;
    public ObservableCollection<CategoryModel> Categories
    {
        get { return _Categories; }
        set
        {
            _Categories = value;
            _Categories.Insert(0, new CategoryModel()
            {
                CategoryId = 0,
                CategoryName = " -- Select Category -- "
            });
            SelectedCategory = _Categories[0];
            OnPropertyChanged("Categories");

        }
    }
0
votes

A little late but..

A more simple way would be to add a dummy data item to the list with parameter IsDummy=true and make sure it is not HitTestVisable and its hight is 1 pixel (using a Converter) so it wont be seen.

Than just register to SelectionChanged and in it, set the index to the dummy item index.

It works like a charm and this way you don't mess with the style and colors of the ComboBox or your application theme.

0
votes
InitializeComponent()
yourcombobox.text=" -- Select Team --";

The above code demonstrates the simplest way to achieve it. After window load, declare the text of the combobox, using the .Text property of the combobox. This can be extended to the DatePicker, Textbox and other controls as well.

0
votes

EDIT: Per comments below, this is not a solution. Not sure how I had it working, and can't check that project.

It's time to update this answer for the latest XAML.

Finding this SO question searching for a solution to this question, I then found that the updated XAML spec has a simple solution.

An attribute called "Placeholder" is now available to accomplish this task. It is as simple as this (in Visual Studio 2015):

<ComboBox x:Name="Selection" PlaceholderText="Select...">
    <x:String>Item 1</x:String>
    <x:String>Item 2</x:String>
    <x:String>Item 3</x:String>
</ComboBox>
0
votes

I did it before binding the combobox with data from database in codebehind like this -

Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;
0
votes
  1. Put a label on top of the combobox.

  2. Bind the content of the label to to the combobox Text property.

  3. Set the opacity of the combobox to zero , Opacity=0.

  4. Write default text in the combobox Text property

          <ComboBox Name="cb"
            Text="--Select Team--" Opacity="0" 
            Height="40" Width="140" >
             <ComboBoxItem Content="Manchester United" />
             <ComboBoxItem Content="Lester" />
         </ComboBox>
     </Grid>
    
-2
votes

Only set the IsEditable attribute to true

<ComboBox Name="comboBox1"            
          Text="--Select Team--"
          IsEditable="true"  <---- that's all!
          IsReadOnly="true"/>
-3
votes

I know this is semi old but what about this way:

<DataTemplate x:Key="italComboWM">
    <TextBlock FontSize="11" FontFamily="Segoe UI" FontStyle="Italic" Text="--Select an item--" />
</DataTemplate>

<ComboBox EmptySelectionBoxTemplate="{StaticResource italComboWM}" />