Как отобразить текст по умолчанию "--Select Team -" в поле со списком при загрузке страницы в WPF?

В приложении WPF, в приложении MVP у меня есть поле со списком, для которого я отображаю данные, извлеченные из базы данных. Прежде чем элементы, добавленные в поле со списком, я хочу отобразить текст по умолчанию, такой как

" -- Выбрать команду --"

так, чтобы при загрузке страницы он отображался и при его выборе текст должен быть очищен, а элементы отображаться.

Выбор данных из БД происходит. Мне нужно отображать текст по умолчанию, пока пользователь не выберет элемент из поля со списком.

Пожалуйста, ведите меня

24 ответа

Я нашел самый простой способ сделать это:

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

Вам, очевидно, нужно будет добавить другие варианты, но это, вероятно, самый простой способ сделать это.

Однако у этого метода есть один недостаток, который заключается в том, что хотя текст внутри поля со списком не будет редактируемым, его можно будет выбирать. Однако, учитывая низкое качество и сложность каждой альтернативы, которую я нашел на сегодняшний день, это, вероятно, лучший вариант.

Вы можете сделать это без какого-либо кода, используя IValueConverter,

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

Здесь у вас есть класс конвертера, который вы можете использовать повторно.

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
}

И, наконец, вам нужно объявить свой конвертер в разделе ресурсов.

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

Где Конвертеры - это место, где вы разместили класс конвертеров. Примером является:

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

Очень хорошая вещь в этом подходе - отсутствие повторения кода в вашем коде.

Мне нравится ответ Tri Q, но эти конвертеры значений очень неудобны. PaulB сделал это с помощью обработчика событий, но это также не нужно. Вот чистое решение XAML:

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

Никто не сказал, что чистое решение xaml должно быть сложным. Вот простой, с 1 триггером данных в текстовом поле. Маржа и позиция по желанию

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

Установите IsEditable=True для элемента Combobox. Это отобразит свойство Text в Combobox

Я не знаю, поддерживается ли он напрямую, но вы можете наложить комбо на ярлык и установить его скрытым, если выделение не равно нулю.

например.

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

Тогда в выборе изменился обработчик...

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

На основании ответа IceForge я подготовил многоразовое решение:

стиль xaml:

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

пример использования:

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

Не пробовал с комбо-боксами, но это работало для меня с другими элементами управления...

пост в блоге

Он использует слой adorner здесь, чтобы отобразить водяной знак.

Решение HappyNomad было очень хорошим и помогло мне в итоге прийти к этому немного другому решению.

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

Самый простой способ - использовать CompositeCollection для объединения текста по умолчанию и данных из базы данных непосредственно в ComboBox, например

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

А в Ресурсах определите StaticResource, чтобы связать параметры ComboBox с вашим DataContext, потому что прямое связывание в CollectionContainer не работает правильно.

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

Таким образом, вы можете определить параметры ComboBox только в xaml, например

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

Я бы порекомендовал следующее:

Определить поведение

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;
        }
    }
}

Пользовательское поведение

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

Ответ IceForge был довольно близок, и AFAIK - самое простое решение этой проблемы. Но он что-то пропустил, так как он не работал (по крайней мере для меня, он никогда не отображал текст).

В конце концов, вы не можете просто установить для свойства "Видимость" TextBlock значение "Скрытый", чтобы его можно было скрыть, когда выбранный элемент поля со списком не равен нулю; вы должны установить его таким образом по умолчанию (поскольку вы не можете проверить не нуль в триггерах, используя Setter в XAML в том же месте, что и триггеры.

Вот фактическое решение, основанное на его, отсутствующий Сеттер, помещенный непосредственно перед Триггерами:

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

РЕДАКТИРОВАТЬ: Согласно комментариям ниже, это не решение. Не уверен, как у меня это работает, и не могу проверить этот проект.

Пришло время обновить этот ответ для последней версии XAML.

Найдя этот SO-вопрос в поисках решения этого вопроса, я обнаружил, что у обновленной спецификации XAML есть простое решение.

Атрибут под названием "Заполнитель" теперь доступен для выполнения этой задачи. Это так просто (в 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>

Я сделал это перед тем, как связать комбинированный список с данными из базы данных в коде, как это -

Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;

Я считаю, что водяной знак, упомянутый в этом посте, будет хорошо работать в этом случае.

Требуется немного кода, но вы можете использовать его для любого комбинированного списка или текстового поля (и даже паролей), поэтому я предпочитаю этот способ

Я использую класс IsNullConverter в моем проекте, и он работал для меня. Вот код для него на C#, создайте папку с именем Converter и добавьте этот класс в эту папку, так как используемый триггер не поддерживает значение вместо нуля, а IsNullConverter просто делает это

 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.");
    }
}

добавить пространство имен в файл xaml, как это.

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

средства

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

используйте эту строку под ресурсами, чтобы сделать ее доступной через код xaml

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

вот код xaml, я использовал здесь триггер, чтобы при выборе элемента в выпадающем списке видимость вашего текста становилась ложной.

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

Это старо, но вот моя идея в стиле MVVM. Я использую фреймворк Stylet MVVM. Это просмотр:

      <UserControl x:Class="ComboBoxWithPlaceholderTextView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:s="https://github.com/canton7/Stylet"
         mc:Ignorable="d"
         >
<Grid>
    <ComboBox
                ItemsSource="{Binding ItemsSource}"
                SelectedItem="{Binding SelectedItem}"
                DropDownOpened="{s:Action DropDownOpened}"
                DropDownClosed="{s:Action DropDownClosed}"
                IsDropDownOpen="{Binding IsDropDownOpened}"
                />

</Grid>

а потом в ViewModel

      public class ComboBoxWithPlaceholderTextViewModel : Screen
{
    private List<string> _itemsSource;
    private string _placeholderText;
    private string _selectedItem;
    private bool _isDropDownOpened;

    public bool IsDropDownOpened
    {
        get => _isDropDownOpened;
        set
        {
            if (value == _isDropDownOpened)
            {
                return;
            }
            SetAndNotify(ref _isDropDownOpened, value);
        }
    }

    public string SelectedItem
    {
        get
        {
            return _selectedItem;
        }

        set
        {
            SetAndNotify(ref _selectedItem, value);
        }
    }

    public string PlaceholderText
    {
        get { return _placeholderText; }
        set 
        {
            if (value == _placeholderText)
            {
                return;
            }
            SetAndNotify(ref _placeholderText, value);
        }
    }

    public List<string> ItemsSource
    {
        get { return _itemsSource; }
        set 
        { 
            SetAndNotify(ref _itemsSource, value);
            if (!IsDropDownOpened && (string.IsNullOrEmpty(SelectedItem) || !SelectedItem.Equals(PlaceholderText)))
            {
                ItemsSource.Insert(0, PlaceholderText);
                SelectedItem = ItemsSource[0];
            }
        }
    }

    public void DropDownOpened()
    {
         ItemsSource.RemoveAt(0);
        SelectedItem = null;
    }

    public void DropDownClosed()
    {
        if (SelectedItem is null)
        {
            ItemsSource.Insert(0, PlaceholderText);
            SelectedItem = ItemsSource[0];
        }
    }
}

Таким образом, мне не нужно заботиться о том, будет ли текст избегать комбо, но я должен заботиться о том, выбран ли текст-заполнитель.

Решение
1. Поместите этикетку на верхнюю часть комбинированного списка.
2. Привязать содержимое метки к свойству текста в выпадающем списке.
3. Установите непрозрачность комбинированного списка на ноль, непрозрачность = 0.
4. Введите текст по умолчанию в поле со списком.

 <Grid>
        <Label Content="{Binding ElementName=cb, Path=Text}" 
         VerticalContentAlignment="Center"
         HorizontalContentAlignment="Center"
         Height="{Binding ElementName=cb, Path=Height}"
         Width="{Binding ElementName=cb, Path=Width}"/>

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

Не лучшая практика.. но работает нормально...

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

Код позади

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;
            }           
    }
}
InitializeComponent()
yourcombobox.text=" -- Select Team --";

Приведенный выше код демонстрирует самый простой способ достичь этого. После загрузки окна объявите текст комбинированного списка, используя свойство.Text комбинированного списка. Это может быть расширено до DatePicker, Textbox и других элементов управления.

Немного поздно, но..

Более простым способом было бы добавить фиктивный элемент данных в список с параметром IsDummy = true и убедиться, что это не HitTestVisable, а его высота составляет 1 пиксель (с помощью конвертера), чтобы его не было видно.

Затем просто зарегистрируйтесь в SelectionChanged и установите в нем индекс фиктивного элемента.

Это работает как очарование, и таким образом вы не связываетесь со стилем и цветами ComboBox или темой вашего приложения.

// Код XAML

// 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");

        }
    }

Только установите для атрибута IsEditable значение true

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

Я знаю, что это полу старое, но как насчет этого:

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

<ComboBox EmptySelectionBoxTemplate="{StaticResource italComboWM}" />
Другие вопросы по тегам