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


109

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

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

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

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

Пожалуйста, направь меня

Ответы:


108

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

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

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

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


Отличный ответ Крис! Я бы просто добавил Focusable = "True", но это просто косметическое изменение.
Слависа

6
прекрасный ответ Крис. Одно свойство может сделать такую ​​большую разницу: D
Aster Veigas

4
Focusable="False" IsEditable="True" IsReadOnly="True"
Камил Лелонек 01

1
+1. К сожалению, это не работает со смешанными элементами (например, если элементы combobox являются изображениями).
greenoldman

11
Простое и рабочее решение. Элементы управления WPF имеют такого рода проблемы по всей структуре. Кое-где в элементах управления отсутствуют функции, которые понадобятся большинству разработчиков. В результате разработчики тратят время на поиск решений, покупку сторонних альтернативных элементов управления или реализацию обходных путей ... Использует ли команда WPF свои элементы управления для собственных разработок?
Damn Vegetables

90

Вы можете сделать это без какого-либо кода, используя файл 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"

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


Я хотел бы использовать это, но, похоже, это не разрешено в тех случаях, когда поле со списком является заголовком таблицы данных. . . XAML выдает ошибку, что заголовок уже определен (или, возможно, не может быть определен более одного раза). Любые идеи? Я думаю просто использовать параметр нулевого значения, который затем позволит выполнить сброс, выбрав его, но кажется немного небрежным.
Пол Гибсон

1
Большая причина, по которой это отличное решение, заключается в том, что почти каждый проект WPF с привязкой к данным использует NullToVisibilityConverter, поэтому большую часть времени он уже существует - с таким же успехом можно его использовать!
участник

2
На самом деле вы можете использовать здесь, DataTriggerчтобы избежать даже кода конвертера :)
Билли Онил

49

Мне нравится ответ 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>

33

Никто не сказал, что чистое решение 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>

5
Мне нужно было переместить "Visibility =" Hidden "в триггер данных. Затем все заработало, как ожидалось. Определенно, самый простой подход, который я когда-либо видел. Для повторного использования я перенес стиль в ресурс
Митч,

Ответ @Mitch IceForce у меня не работает, что вы изменили, чтобы он работал?
Крис

1
@Chris Я думаю, он имел в виду добавление <Setter Property="Visibility" Value="Hidden"/>за пределами триггера (внутри стиля) и удаление Visibility="Hidden"из фактического элемента текстового блока
Восстановите Монику, пожалуйста,

@Mitch, как вы переместите стиль Textblock в ресурс для повторного использования, если у вас есть ElementName в DataTrigger, указывающий на конкретный объект (mybox)? Есть ли способ указать это имя в общем виде?
CrApHeR


16

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

например.

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

1
Вместо того, чтобы создавать обработчик SelectionChanged, видимость TextBlock может быть установлена ​​в XAML.
aliceraunsbaek

6

Основываясь на ответе 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>

Ницца. Я расширил его, привязав DataContext TextBlock с использованием относительного источника, чтобы избежать необходимости устанавливать имя. См. Следующий комментарий для разметки (код в комментариях SO выглядит некрасиво)
Саша

<TextBlock DataContext = "{Binding Path = Children [0] .SelectedItem, RelativeSource = {RelativeSource AncestorType = Grid}}" Text = "- Выберите проект -" Style = "{StaticResource ComboBoxSelectOverlay}" />
Саша,

4

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

пост в блоге ageektrapped

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


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

Хороший материал, он не только решает проблему ComboBox, но теперь я могу избавиться от сборки WPF Tools и просто использовать ее в своих текстовых полях вместо элемента управления WatermarkedTextBox, так что это полная победа :) - о, кстати, это A Geek Trapped не Согласная ловушка!
dain

2

Решение 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>

2

Самый простой способ - использовать 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>

1

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

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

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

Это работает как шарм для отдельного поля со списком. Но когда я использую его с более чем одним комбо, он выдает ошибку (но компилируется и работает нормально) «Свойство DefaultText уже зарегистрировано в ComboBox». Я упомянул об исправлении в своем блоге.
Ромеш Д. Нириэлла

Спасибо за указание на это. Я не мог произвести эту ошибку на моем компьютере. Однако я согласен, что typeof (ComboBoxBehaviors) следует передавать в третьем параметре RegisterAttached вместо typeof (ComboBox).
Усман Зафар

Хотя этот пост немного устарел, я не понимаю, как он может работать. ГК комбо устанавливается с помощью триггеров с несколькими условиями. Попробуйте разместить одну комбинацию на сетке и вручную установить для bg значение «красный». Это не влияет на область, где вы хотите разместить водяной знак. Это может повлиять только на bg за раскрывающейся панелью. Лучшее решение - скопировать шаблон элемента управления combobox и добавить несколько триггеров и стилей, чтобы нарисовать визуальную кисть, состоящую из текстового блока, на фоне границы.
Newclique

1

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

В конце концов, вы не можете просто установить для свойства «Visibility» TextBlock значение «Hidden», чтобы оно было скрыто, когда выбранный элемент поля со списком не равен нулю; вы должны установить его таким образом по умолчанию (поскольку вы не можете проверить, не является ли значение NULL в триггерах , используя 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>

1

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

Пришло время обновить этот ответ для последней версии 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>

Я использовал это решение - неужели отрицательный избиратель заботится о подробностях? Конечно, я не эксперт XAML, но это сработало.
Робб Сэдлер,

1
Хотя я не голосую против, я полагаю, что вы были проголосованы против, потому что PlaceholderTextв System.Windows.ComboBoxклассе нет собственности . Это вопрос о WPF, а не о WinForms.
Шеридан

Черт, это странно - я знаю, что работал над приложением XAML, и я знаю, что только что обнаружил это и увидел, как это работает. Может быть, в проект было включено расширение? IDK - с тех пор я посмотрел и наверняка в ComboBox нет заполнителя. Я не могу вернуться к проекту, над которым работал - старому клиенту. тьфу.
Робб Сэдлер,

2
Вы не ошиблись, но это не для WPF. Это есть в UWP ComboBox, см. Эту страницу: msdn.microsoft.com/en-us/library/windows/apps/…
laishiekai

0

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

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

0

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

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


0

Я использую в своем проекте класс IsNullConverter, и он у меня сработал. вот код для него на С #, создайте папку с именем Converter и добавьте этот класс в эту папку, поскольку используемый триггер не поддерживает значение, а не null, и 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>

0

// Код XAML

// Код ViewModel

    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

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

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

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

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


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

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


0

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

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

1
Это просто добавляет текст как вариант в раскрывающемся списке. OP не об этом просил.
Дин Фридланд

речь идет о добавлении текста по умолчанию, и я сделал это таким образом
Atiq Baqi

0
  1. Поместите метку поверх поля со списком.

  2. Привяжите содержимое метки к свойству текста поля со списком.

  3. Установите прозрачность поля со списком на ноль, непрозрачность = 0.

  4. Записать текст по умолчанию в свойство текста поля со списком

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

-2

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

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

-3

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

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

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

2
ComboBoxне имеет EmptySelectionBoxTemplateсобственности.
Novitchi S
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.