Dans une application WPF, dans l'application MVP, j'ai une liste déroulante pour laquelle j'affiche les données extraites de Database. Avant que les éléments ajoutés à la liste déroulante, je veux afficher le texte par défaut tel que
" -- Choisis une équipe --"
de sorte que sur pageload, il affiche et sélectionne le texte doit être effacé et les éléments doivent être affichés.
La sélection des données de la base de données est en cours. Je dois afficher le texte par défaut jusqu'à ce que l'utilisateur sélectionne un élément de la liste déroulante.
Guidez-moi s'il-vous-plaît
Le moyen le plus simple que j'ai trouvé est le suivant:
<ComboBox Name="MyComboBox"
IsEditable="True"
IsReadOnly="True"
Text="-- Select Team --" />
Vous aurez évidemment besoin d'ajouter vos autres options, mais c'est probablement la manière la plus simple de le faire.
Il existe toutefois un inconvénient à cette méthode: si le texte de votre liste déroulante ne peut pas être modifié, il est toujours sélectionnable. Cependant, étant donné la qualité médiocre et la complexité de toutes les alternatives que j'ai trouvées à ce jour, il s'agit probablement de la meilleure option.
Vous pouvez le faire sans code derrière en utilisant une IValueConverter
.
<Grid>
<ComboBox
x:Name="comboBox1"
ItemsSource="{Binding MyItemSource}" />
<TextBlock
Visibility="{Binding SelectedItem, ElementName=comboBox1, Converter={StaticResource NullToVisibilityConverter}}"
IsHitTestVisible="False"
Text="... Select Team ..." />
</Grid>
Ici vous avez la classe de convertisseur que vous pouvez réutiliser.
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
}
Et enfin, vous devez déclarer votre convertisseur dans une section de ressources.
<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
Où Convertisseurs est l'endroit où vous avez placé la classe de convertisseur. Un exemple est:
xmlns:Converters="clr-namespace:MyProject.Resources.Converters"
La très belle chose à propos de cette approche est de ne pas répéter le code dans votre code derrière.
J'aime la réponse de Tri Q, mais ces convertisseurs de valeur sont difficiles à utiliser. PaulB l'a fait avec un gestionnaire d'événements, mais c'est également inutile. Voici une solution XAML pure:
<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>
Personne n'a dit qu'une solution pure en xaml devait être compliquée. Voici un exemple simple, avec 1 déclencheur de données dans la zone de texte. Marge et position comme souhaité
<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>
Définissez IsEditable = True sur l'élément Combobox. Ceci affichera la propriété Text de la liste déroulante.
Je ne sais pas s'il est directement pris en charge mais vous pouvez superposer le combo avec une étiquette et le définir sur masqué si la sélection n'est pas nulle.
par exemple.
<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>
Puis dans la sélection a changé de gestionnaire ...
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
txtSelectTeam.Visibility = comboBox1.SelectedItem == null ? Visibility.Visible : Visibility.Hidden;
}
Basé sur la réponse d'IceForge j'ai préparé une solution réutilisable:
style 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>
exemple d'utilisation:
<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>
Pas essayé avec les combos mais cela a fonctionné pour moi avec d'autres contrôles ...
Il utilise la couche adorner ici pour afficher un filigrane.
La solution de HappyNomad était très bonne et m'a permis d'arriver finalement à cette solution légèrement différente.
<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>
Le moyen le plus simple consiste à utiliser CompositeCollection pour fusionner le texte et les données par défaut de la base de données directement dans ComboBox, par exemple.
<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>
Et dans Ressources, définissez StaticResource pour lier les options ComboBox à votre DataContext, car la liaison directe dans CollectionContainer ne fonctionne pas correctement.
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="MyComboOptions" />
</Window.Resources>
De cette façon, vous pouvez définir vos options ComboBox uniquement dans xaml, par exemple.
<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>
La réponse d'IceForge était assez proche, et c'est autant que je sache, la solution la plus simple à ce problème. Mais il manquait quelque chose, car cela ne fonctionnait pas (du moins pour moi, le texte ne s'affiche jamais).
En fin de compte, vous ne pouvez pas simplement définir la propriété "Visibility" du TextBlock sur "Caché" pour qu'il soit masqué lorsque l'élément sélectionné de la liste déroulante n'est pas null; vous devez le configurer de cette façon par défaut (puisque vous ne pouvez pas cocher non dans les déclencheurs , en utilisant un Setter en XAML au même endroit que les déclencheurs.
Voici la solution réelle basée sur la sienne, le Setter manquant étant placé juste avant les déclencheurs:
<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>
Je recommanderais ce qui suit:
Définir un comportement
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;
}
}
}
Utilisateur le comportement
<ComboBox Name="cmb" Margin="72,121,0,0" VerticalAlignment="Top"
local:ComboBoxBehaviors.DefaultText="-- Select Team --"/>
EDIT: Selon les commentaires ci-dessous, ce n'est pas une solution. Je ne sais pas comment j'ai fonctionné et je ne peux pas vérifier ce projet.
Il est temps de mettre à jour cette réponse pour le dernier XAML.
En trouvant cette SO question en cherchant une solution à cette question, j’ai alors constaté que la spécification XAML mise à jour offrait une solution simple.
Un attribut appelé "Placeholder" est maintenant disponible pour accomplir cette tâche. C'est aussi simple que cela (dans 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>
Je l'ai fait avant de lier la combobox avec les données de la base de données dans codebehind comme ceci -
Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;
Un peu tard mais ..
Une méthode plus simple consisterait à ajouter un élément de données factice à la liste avec le paramètre IsDummy = true et à vous assurer qu'il ne s'agit pas de HitTestVisable et que sa hauteur est de 1 pixel (à l'aide d'un convertisseur) afin qu'il ne soit pas visible.
Il suffit ensuite de s’inscrire à SelectionChanged et de définir l’index sur l’index factice.
Cela fonctionne comme un charme et vous éviterez ainsi le style et les couleurs de la ComboBox ou du thème de votre application.
Solution
1. Placez une étiquette sur la liste déroulante.
2. Liez le contenu de l'étiquette à la propriété Text de la liste déroulante.
3. Définissez l'opacité de la liste déroulante sur zéro, Opacité = 0.
4. Écrire du texte par défaut dans la propriété Text de la liste déroulante
<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>
J'utilise une classe IsNullConverter dans mon projet et cela a fonctionné pour moi . Voici le code correspondant en c #, créez un dossier nommé Converter et ajoutez cette classe dans ce dossier, car le déclencheur utilisé ne prend pas en charge la valeur pour plutôt que null, et IsNullConverter vient de le faire
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.");
}
}
ajoutez l'espace de noms dans le fichier xaml comme ceci.
xmlns:Converters="clr-namespace:TymeSheet.Converter"
veux dire
xmlns:Converters="clr-namespace:YourProjectName.Converter"
utilisez cette ligne sous les ressources pour la rendre disponible via le code xaml
<Converters:IsNullConverter x:Key="isNullConverter" />
voici le code xaml, j’ai utilisé ici le déclencheur pour que chaque fois qu’un élément est sélectionné dans la liste déroulante, la visibilité de votre texte devient fausse.
<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>
// Code XAML
// Code 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");
}
}
InitializeComponent()
yourcombobox.text=" -- Select Team --";
Le code ci-dessus illustre le moyen le plus simple d'y parvenir. Après le chargement de la fenêtre, déclarez le texte de la liste déroulante, à l'aide de la propriété .Text de la liste déroulante. Cela peut être étendu au DatePicker, à la zone de texte et à d’autres contrôles.
Pas la meilleure pratique..mais marche bien ...
<ComboBox GotFocus="Focused" x:Name="combobox1" HorizontalAlignment="Left" Margin="8,29,0,0" VerticalAlignment="Top" Width="128" Height="117"/>
Code derrière
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;
}
}
}
Je crois qu'un filigrane comme mentionné dans cet article fonctionnerait bien dans ce cas
Il faut un peu de code, mais vous pouvez le réutiliser pour n’importe quelle liste déroulante ou zone de texte (et même pour les boîtes de mots de passe).