Setting SelectedItem in ListView when user clicks in ItemTemplate Textbox - wpf

I have the following ListView (simplified):
<ListView Name="lvwNotes" KeyUp="lvwNotes_KeyUp">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<DockPanel Background="LightGray">
<TextBlock DockPanel.Dock="Right" Text="{Binding Path=Author}" />
<TextBlock Text="{Binding Path=Timestamp}" />
</DockPanel>
<TextBox Text="{Binding Path=Text}"
GotFocus = "lvwNotes_TextBox_GotFocus"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Changing the selected item through a click only works when the user clicks on the DockPanel with the TextBlocks, but not on clicking the TextBox. What I want to achieve is to set the selected item to that one containing the TextBox into which the user clicked.
I managed to get through to the ListViewItem related to the TextBox:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e) {
DependencyObject o = Tools.GetAncestorByType((DependencyObject)sender, typeof(ListViewItem));
if (!o.Equals(null)) {
// code to select this ListViewItem
}
}
But setting
lvwNotes.SelectedIten = o ;
remains without effect. I've tried also some tricks with Dispatcher.BeginInvoke, but to be honest, I don't exactly know what I'm doing there.

Add this to your code
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>

The DataContext unless explicitly changed in the DataTemplate is the current item, hence:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (TextBox)sender;
lvwNotes.SelectedItem = tb.DataContext;
}

Related

WPF combobox in a datagrid group header

Is it possible to use a combobox in a datagrid group header to set a selected value for each cells of the appropriate column (this one contains comboboxes)?
Or is there any better solution to set multiple combobox values in a column at the same time?
You can put a ComboBox in the group header like this:
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center" />
<TextBlock Text="{Binding Path=ItemCount}" Margin="5,0,2,0" VerticalAlignment="Center" />
<TextBlock Text="Items" VerticalAlignment="Center" />
<ComboBox Margin="10,0" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Content="1" />
<ComboBoxItem Content="2" />
<ComboBoxItem Content="3" />
</ComboBox>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
The SelectionChanged event handler looks something like this:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DependencyObject dependencyObject = sender as DependencyObject;
// Walk up the visual tree from the ComboBox to find the GroupItem.
while (dependencyObject != null && dependencyObject.GetType() != typeof(GroupItem))
{
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
}
GroupItem groupItem = dependencyObject as GroupItem;
CollectionViewGroup collectionViewGroup = groupItem.Content as CollectionViewGroup;
// Iterate the items of the CollectionViewGroup for the GroupItem.
foreach (object item in collectionViewGroup.Items)
{
// Your logic for changing values here...
}
}

Hide Datagrid Column based on its Property name

I have a DataGrid defined as follows :
<DataGrid Name="dtMydatagrid" Margin="10,10,10,10" RowHeight="20" AutoGenerateColumns="True" ItemsSource="{Binding}" Height="auto" Width="auto">
<DataGrid.Columns>
<DataGridTemplateColumn Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="TXT" Background="Transparent" Width="15" IsReadOnly="True" Visibility="Hidden" Margin="0,0,0,0"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsBKM}" Value="true">
<Setter Property="Background" Value="AQUA" TargetName="TXT"/>
<Setter Property="Visibility" Value="Visible" TargetName="TXT"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Now, I have a boolean property in my class named IsBKM to which the DataGridTemplateColumn is bounded.
So, it is displayed by as CheckBox.
I don't want to display the IsBKM column in my DataGrid. Can I use a trigger and hide the column whose name is IsBKM or any different solution?
Thanks in advance.
You could handle the DataGrid.AutoGeneratedColumns Event and set the column's Visibility property from there. You should be able to do something like this:
private void DataGridAutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (dataGrid != null && IsBKM) dataGrid.Columns[0].Visible = false;
}
UPDATE >>>
You can use the e.Column.Header property to check the name of the column and then use that instead. However, your column has no Header currently set. You could also set the column name (in XAML) and then check for that Name value instead of using the Header property:
private void DataGridAutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
if (e.Column.Name == "IsBKM" && IsBKM)
{
e.Column.Visibility = Visibility.Collapsed;
}
}

Dynamic template for a ComboBox in ListBox

I have a ListBox with an embedded ComboBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Width="100" IsEditable="False" Height="20">
<TextBlock Text="Opt#1"></TextBlock>
<TextBlock Text="Opt#2"></TextBlock>
<TextBlock Text="Opt#3"></TextBlock>
</ComboBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd like to present the ComboBox as a simple text (e.g. TextBlock) when a ListBox row is not selected, and show it as a ComboBox when the ListBox row is selected.
I was thinking that replacing ComboBox template dynamically would do the trick. How to accomplish that?
Thanks,
Leszek
The best way to swap templates is to use the ItemTemplateSelector propery of the ListBox and set it to a class you create which inherits from DataTemplateSelector.
Here is a link that provides an example: http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
I would simply use a style that replace the ListBox.ItemTemplate whenever the ListBoxItem becomes selected.
Here's a quick example
<ListBox.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox SelectedItem="{Binding }">
<ComboBoxItem>Opt#1</ComboBoxItem>
<ComboBoxItem>Opt#2</ComboBoxItem>
<ComboBoxItem>Opt#3</ComboBoxItem>
</ComboBox>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template" Value="{StaticResource ComboBoxTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
I'd actually suggest using IsKeyboardFocusWithin instead of IsSelected as the trigger property, because templates can let you interact with them without setting the item as selected.
Thanks Josh and Rachel for pointing me in a right direction.
I came up with a solution similar to the one suggested by Rachel. My problem was I could not make ItemTemplateSelector work and I did not know how to pass the state IsSelected from my listbox. I also could not use DataTemplate because my ListBox item is much more complex than a single element (I simplified it in my previous post for the sake of example).
Anyway, I came up with the following solution. It's not very elegant but it works:
I defined a new style in Application resources:
<Style x:Key="TextBlockTemplate" TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding}" Margin="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I attached SelectionChanged and PreviewMouseDown handlers to my ListBox:
I defined MyListBox_PreviewMouseDown:
private void MyListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Grab the selected list box item.
object element = (e.OriginalSource as FrameworkElement).DataContext;
var item = MyListBox.ItemContainerGenerator.ContainerFromItem(element)
as ListBoxItem;
// Mark the row in the ListBox as selected.
if (item != null)
item.IsSelected = true;
}
I defined MyListBox_SelectionChanged:
private ComboBox prevComboBox = null;
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Grab the list box.
ListBox list = sender as ListBox;
// Although there could be only one item selected,
// we iterate over all selected items.
foreach (MyDataItem dat in list.SelectedItems)
{
var item = list.ItemContainerGenerator.ContainerFromItem(dat) as ListBoxItem;
// FindElement is a helper method to find an element in a visual tree.
ComboBox cbo = FindElement(item, "MyComboBox") as ComboBox;
if (cbo != prevComboBox)
{
cbo.Style = null;
if (prevComboBox != null)
prevComboBox.Style =
(Style)Application.Current.Resources["TextBlockTemplate"];
prevComboBox = cbo;
}
}
}
Thanks,
Leszek

WPF Usercontrol, TextBox PropertyChanged event not firing

I have a usercontrol with a DependencyProperty of Answer which is attached to a TextBox.
I have queried the database and bound the answer to the usercontrol and the correct value is displayed.
The issue occurs when I edit the TextBox, the PropertyChanged event is not firing and thus preventing me from saving the correct value back to the database.
Please see below my code.
Usercontrol:
<Grid>
<StackPanel>
<TextBlock Name="txtbQuestion" TextWrapping="Wrap" Text="Question" Foreground="Black" Margin="5" Style="{DynamicResource Label}" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" ></TextBlock>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Name="txtAnswer" Margin="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" >
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbMultiLine, Path=IsChecked}" Value="True">
<Setter Property="TextBox.TextWrapping" Value="Wrap" />
<Setter Property="TextBox.Height" Value="100" />
<Setter Property="TextBox.AcceptsReturn" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox Name="cbMultiLine" Content="MultiLine" Margin="5" FontFamily="Georgia" Grid.Column="1" />
</Grid>
<Line Fill="Black" Margin="4" />
</StackPanel>
</Grid>
Usercontrol.cs:
public partial class ConditionQuestion : UserControl
{
public static readonly DependencyProperty AnswerProperty =
DependencyProperty.Register("Answer", typeof(string), typeof(ConditionQuestion), new UIPropertyMetadata(null, Answer_PropertyChanged));
public static readonly DependencyProperty QuestionProperty =
DependencyProperty.Register("Question", typeof(string), typeof(ConditionQuestion), new UIPropertyMetadata(null, Question_PropertyChanged));
public ConditionQuestion()
{
InitializeComponent();
}
public string Answer
{
get { return (string)GetValue(AnswerProperty); }
set { SetValue(AnswerProperty, value); }
}
public string Question
{
get { return (string)GetValue(QuestionProperty); }
set { SetValue(QuestionProperty, value); }
}
private static void Answer_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ConditionQuestion)source).txtAnswer.Text = (string)e.NewValue;
}
private static void Question_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ConditionQuestion)source).txtbQuestion.Text = (string)e.NewValue;
}
}
Declaring instance of Usercontrol:
<ListBox ItemContainerStyle="{StaticResource noSelect}" ItemsSource="{Binding Answer}"
Name="lbQuestions" BorderBrush="#E6E6E6" >
<ListBox.ItemTemplate>
<DataTemplate>
<my:ConditionQuestion Question="{Binding ConditionReportFormQuestions.Question}"
Answer="{Binding Answer, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I apologize in advance, I am {relatively} new to WPF. Can anyone see where I may be going wrong with this?
I have successfully got my other usercontrols binding and updating (this code is almost an exact copy) but the answers on them are ListBox selections where-as this usercontrol is binding to a TextBox.
Thanks in advance,
Kohan.
You have not bound the text box to the answer property. What you have done is put a changed handler on your answer property and when it is changed you manually set the text boxes text property.
Your code should look something like this
<TextBlock
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:ConditionQuestion}}, Path=Answer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
this is a binding between the textbox and the property Answer on the class ConditionQuestion (the user control). Whenever the Answer property changes on the user control the text box will update and whenever you change the text in the textbox the Answer property will be updated. With this code you can remove your Answer_PropertyChanged method it is no longer neccessary. the binding takes care of it

How to display default text "--Select Team --" in combo box on pageload in WPF?

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
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.
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.
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>
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>
Set IsEditable="True" on the ComboBox element. This will display the Text property of the ComboBox.
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;
}
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>
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.
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>
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>
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 --"/>
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>
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;
}
}
}
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
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>
//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");
}
}
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.
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.
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>
I did it before binding the combobox with data from database in codebehind like this -
Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;
Put a label on top of the combobox.
Bind the content of the label to to the combobox Text property.
Set the opacity of the combobox to zero , Opacity=0.
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>
This is old, but here's my idea in kind of MVVM style. I'm using Stylet MVVM framework.
This is View:
<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>
and then in 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];
}
}
}
In this way I don't have to care if text will escape combo, but I have to care if placeholder text is selected.
Only set the IsEditable attribute to true
<ComboBox Name="comboBox1"
Text="--Select Team--"
IsEditable="true" <---- that's all!
IsReadOnly="true"/>
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}" />

Resources