WPF showing control with DataTrigger skips it in TabOrder - wpf

I would like to show 'additional details' Textbox when the previous TextBox's value is not 0.00 (zero). This is easily accomplished with DataTriggers:
<TextBox Name="FirstTB" Text="{Binding Amount, StringFormat=F2}"/><!--when this TB is not 0-->
<TextBox Name="SecondTB"> <!--show this TB-->
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Amount}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="ThirdTB"/>
The issue is, however, when changing value of FirstTB to <> 0 and pressing Tab the focus jumps to the ThirdTB instead of SecondTB (even though SecondTB now is Visible due to the DataTrigger). How can I fix this issue?
Sadly, UpdateSourceTrigger=PropertyChanged does not appear to be an option due to its interference with StringFormats - it gives terrible UX when you are editing the value, carret jumps around like crazy due to constant StringFormat evaluation. Viewmodel used in example above:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _amount;
public double Amount
{
get { return _amount; }
set
{
_amount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Amount)));
}
}
}

Maybe a stupid work around. But this will do the work:
View:
<TextBox Name="SecondTB" IsVisibleChanged="SecondTB_OnIsVisibleChanged">
<!--show this TB-->
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Amount, Mode=OneWay}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
C#:
private void SecondTB_OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (((bool) e.NewValue))
{
if(!(sender is TextBox txbx)) return;
ThirdTB.GotFocus += ThirdTbOnGotFocus;
}
}
private void ThirdTbOnGotFocus(object sender, RoutedEventArgs e)
{
SecondTB.Focus();
ThirdTB.GotFocus -= ThirdTbOnGotFocus;
}

If you use an IValueConverter you get a very elegant way for your problem. Everytime the value is changed, the logic is called.
public class IsZeroToHiddenConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double d && d == 0)
return Visibility.Hidden;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You calso play around and add ConverterParameter to decide if you want Collapsed or Hidden behavior.
Usage
<Grid>
<Grid.Resources>
<local:IsZeroToHiddenConverter x:Key="IsZeroToHiddenConverter"/>
</Grid.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="Tb1" Grid.Row="0" Width="100" Margin="5" Text="{Binding Path=Amount, StringFormat=F2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="Tb2" Grid.Row="1" Width="100" Margin="5" Visibility="{Binding Path=Amount, Converter={StaticResource IsZeroToHiddenConverter}}"/>
<TextBox x:Name="Tb3" Grid.Row="2" Width="100" Margin="5"/>
</Grid>
</Grid>
Preview

Related

WPF Change icon depending on binding in Control Template

I have a control template that I've defined for a DevExpress TextEdit control and I want to change the Image Source property in the template depending on a binding (e.g. IsIncrease).
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
If the property IsIncrease was set to true then one particular icon should be shown and if the property was set to false then another particular icon should be shown. Anyone know how to do this?
Thanks
You can do it using a converter, you have two choices, either you include 2 Images inside your Grid and you hide/show them using the converter or you use one Image and use the converter to change the Source property (which is probably a better solution). Here's a converter that can handle both situation, followed by both solutions.
BoolConverter :
public class BoolConverter : MarkupExtension, IValueConverter
{
public object TrueValue { get; set; } = Binding.DoNothing;
public object FalseValue { get; set; } = Binding.DoNothing;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool))
return Binding.DoNothing;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == TrueValue)
return true;
if (value == FalseValue)
return false;
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
MainWindow.xaml (solution 1 : two Images) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Collapsed, FalseValue=Visible}}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Visible, FalseValue=Collapsed}}"/>
</Grid>
</ControlTemplate>
MainWindow.xaml (solution 2 : one Image) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="{Binding IsIncrease,
Converter={local:BoolConverter TrueValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png',
FalseValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png'}}"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
To achieve that you need 2 properties to store source for image.I'm gonna use Tag property and write an attached property to store 2 image source and use trigger to change the source
public class AttachedProperty
{
public static readonly DependencyProperty AltSourceProperty =
DependencyProperty.RegisterAttached("AltSource",
typeof(string), typeof(AttachedProperty),
new PropertyMetadata());
public static string GetAltSource(DependencyObject obj)
{
return (string)obj.GetValue(AltSourceProperty);
}
public static void SetAltSource(DependencyObject obj, string value)
{
obj.SetValue(AltSourceProperty, value);
}
}
ControlTemplate
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}" />
<Image x:Name="image"
Width="17"
Height="16"
Margin="0,0,5,0"
HorizontalAlignment="Right"
RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIncrease " Value="True">
<Setter TargetName="image" Property="Source" Value="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsIncrease " Value="False">
<Setter TargetName="image" Property="Source" Value="{Binding Path=(local:AttachedProperty.AltSource), RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<dxe:TextEdit Tag="Image1.jpg" local:AttachedProperty.AltSource="Image2.jpg"/>

Combine design time menu elements into databound menu item binding

Hopefully I got the vernacular right on my question so I don't throw people completely off.
I have a menu that is data bound and using a HierarchicalDataTemplate that handles the various nested types in my binding object. So far everything is working fantastically; but now I would like to add a couple additional menu items to menu items of a certain type, but of course that breaks the binding as I cannot bind to a collection that already contains elements. CompositeCollection seems to be what I am looking for but I keep running into syntax errors when trying to apply that to my HierarchicalDataTemplate.
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type ODIF:PluginContainer}" ItemsSource="{Binding Instance.Devices}">
<HierarchicalDataTemplate.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Instance.Devices}"/>
<MenuItem>One more item!</MenuItem>
<MenuItem>Two more items!</MenuItem>
</CompositeCollection>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Image Source="{Binding PluginIcon}" Width="16" Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding PluginIcon}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding PluginName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ODIF:Device}" ItemsSource="{Binding InputChannels}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Image Source="{Binding StatusIcon}" Width="16" Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding DeviceName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ODIF:DeviceChannel}">
<local:ChannelBox Channel="{Binding}" Width="200" Click="ChannelClicked"/>
</HierarchicalDataTemplate>
</Menu.Resources>
This throws:
The specified value cannot be assigned. The following type was
expected: "BindingBase".
and
Property 'ItemsSource' does not support values of type 'CompositeCollection'.
I guess you have to use a converter for solving your issue.
Let's suppose that MenuModel is a class which represents a menu item. It is really simple:
public class MenuModel
{
private List<MenuModel> children = new List<MenuModel>();
public string Description { get; set; }
public IList Children
{
get
{
return children;
}
}
}
Now we have our XAML:
<Window.Resources>
<collections:ArrayList x:Key="someOtherMenus">
<local:MenuModel Description="Menu A">
<local:MenuModel.Children>
<local:MenuModel Description="SubMenu i" />
<local:MenuModel Description="SubMenu ii" />
</local:MenuModel.Children>
</local:MenuModel>
<local:MenuModel Description="Menu B" />
</collections:ArrayList>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top" Margin="3" ItemsSource="{Binding MenuModels}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate>
<HierarchicalDataTemplate.ItemsSource>
<Binding ConverterParameter="someOtherMenus">
<Binding.Converter>
<local:CompositeCollectionConverter />
</Binding.Converter>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Description}" Margin="3" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<TextBlock Text="text" Margin="10" />
</DockPanel>
So now we can consider the converter implementation:
public class CompositeCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MenuModel menuModel = value as MenuModel;
if (parameter != null)
{
CollectionContainer collectionContainer = new CollectionContainer();
collectionContainer.Collection = menuModel.Children;
CompositeCollection compositeCollection = new CompositeCollection();
compositeCollection.Add(collectionContainer);
collectionContainer = new CollectionContainer();
collectionContainer.Collection = (IEnumerable)App.Current.MainWindow.FindResource(parameter);
compositeCollection.Add(collectionContainer);
return compositeCollection;
}
else
{
return menuModel.Children;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
As you can see it uses its parameter to get a specific resource (in our case I am using the resource called "someOtherMenus", which is an IEnumerable of MenuModels).
Of course the HierarchicalDataTemplate is recursive so the "someOtherMenus" MenuModels will be added to each level (but the first one) of your Menu.
I hope my sample can help you.

Run-time error: InverseBooleanConverter not found

I have a problem attempting to follow the advice in:
How to bind inverse boolean properties in WPF?
When I use with ResourceDictionary, it give run-time error. InverseBooleanConverter not found.
XMAL as follows:
<UserControl x:Class="SMTF.MasterDataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SMTF" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="466" d:DesignWidth="483">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../AppResource.xaml" />
<ResourceDictionary Source="../DefaultStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="200,12,0,0" Name="stkMain" VerticalAlignment="Top" >
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBooleanConverter}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceView}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="View" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
<Grid DockPanel.Dock="Bottom" Margin="0,2,4,2">
<TextBlock HorizontalAlignment="Right">
<ToggleButton x:Name="VisibilityToggle" Focusable="False" Style="{StaticResource SMToggle}" Command ="{Binding ShowNew}" >
</ToggleButton>
<!--<ToggleButton x:Name="VisibilityToggle" Background="Transparent" Command ="{Binding ShowNew}" >
<Image Source="/Image/Add.png" Width="24" />
</ToggleButton>-->
</TextBlock>
</Grid>
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource BoolToVisibility}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceEdit}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="Configure" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
</StackPanel>
</Grid>
</UserControl>
I'm using the same code provided in the link.
ie:
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
in the AppResource XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SMTF">
<vm:InverseBooleanConverter x:Key="InverseBoolToVisibility" />
.....
.....
</ResourceDictionary>
Thanks in advance
NS
An alternative to converters for style related binding is to use Style.Triggers, the following shows a canvas when checkbox IsChecked = false, which would otherwise require an InverseBooleanConverter.
<Canvas x:Name="Overlay">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
<Rectangle Canvas.ZIndex="3" Fill="#99333333" Height="25" Stroke="Transparent" Width="293" Canvas.Left="10" Canvas.Top="-25"/>
</Canvas>
The key you are using is not correct. You resource key is InverseBoolToVisibility, while you have used InverseBooleanConverter as key.
Change the resource key to refer to correct resource as
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBoolToVisibility}}" >
Also your implementation for convereter is wrong. If you want to change the Visibility based on Boolean inverse value update your converter code as:
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a boolean");
if(!(bool)value)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}

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