Как отобразить текст по умолчанию "--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}" />