WPF GridView Column and dynamic cell template based upon data value - wpf

I have a GridView where I can generate the Columns at run time based upon the configuration settings. e.g
Text Column = to show the text in it
Image Column = to display the graphic in there.
but the problem is that the whole column would be shown either of them type.
What I want to do is based upon the data of particular cell, I want
(a) choose a dynamic template
(b) render the contents dynamically (ideally) as FrameworkElement and set the cell content with that.
This means that the each cell can either display text or image. so in first row, 1st column could be text and 2nd row, 1st column could be graphics based upon the data value.
Is there a way to choose the template based upon run time content value of particular cell or completely render a UIElement and then set as value of the cell?
Example:-
Cell data = <bold>Hello. I'm bold</bold> => displays the text as bold (TextBlock Element)
Cell data = <img>test.png</img> => displays the graphic (Image Element)

You can achieve this with Triggers
Here is the sample code. .
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView x:Name="gridview">
<GridViewColumn Header="Type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsImage}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Text goes here"
Foreground="Red"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsImage}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Image goes here"/>
<!--<Image Source="{Binding source}" />-->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And the sample backend code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<myClass> mc = new List<myClass>();
mc.Add(new myClass() { Itemsource = "textblock text", IsImage = false });
mc.Add(new myClass() { Itemsource = "Image source", IsImage = true });
lvUsers.ItemsSource = mc;
}
}
class myClass
{
public string Itemsource { get; set; }
public bool IsImage { get; set; }
}

Related

WPF Datagrid Combobox SelectedItem not Binding to Powershell Object correctly

I am having a bit of trouble correctly binding my Datagrid ComboBox selected item to my PowerShell object. I am using a two way binding for the ComboBox with an 'UpdateSourceTrigger=PropertyChanged' parameter. The source objects correctly get added as items to the ComboBoxes and the source object will update as a selection is changed. However When I save the object or else launch for the first time, the selected items do not get bound. Instead all ComboBoxes are generated as having no selected value.
XAML:
<DataGrid Name="CustomDescription_Fields_DG" HorizontalAlignment="Left" Width="626" Margin="185,113,0,87" IsReadOnly="True" AutoGenerateColumns="False" GridLinesVisibility="None" AlternatingRowBackground="#FFEFEFF2" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Field}" Header="Applied Fields" Width="395"/>
<DataGridTemplateColumn Header="Position" Visibility="Visible" Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Path=Position, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding ItemCount}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground"
Value="{DynamicResource
{x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
WPF application launched:
You can see that when the application is launched that the ComboBox items are correctly bound to the 'ItemCount' field of the ItemsSource object. What should happen (or at least what I'm trying to achieve) is that the selected item should be the item that is defined within the 'Position' field of the ItemsSource object.
This is a breakdown of what is happening:
I'm not to sure what I am doing wrong. Any help would be very much appreciated.
Object being added as Itemssource
You need to define DisplayMemberPath and SelectedItem binding. If you have provided some more code I could have shown you exactly what it should look like. Here is an example from some of my code...
<ComboBox ItemsSource="{Binding Units}" DisplayMemberPath="Symbol" SelectedItem="{Binding SelectedToUnit}" />
Here I created a more complete example using a template datagrid column.
<DataGridTemplateColumn Header="Declared">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding DummyColumn}" DisplayMemberPath="Name" SelectedValuePath="Number"
ItemsSource="{Binding DataContext.DummyCollection, Source={x:Reference dummyElement}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I need a reference to a dummy element to get data context right...
<FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
In the code behind here is my data class and collection
public class DummyClass
{
public int Number { get; set; }
public string Name { get; set; }
}
...
DummyClassCollection = new ObservableCollection<DummyClass>();
DummyClassCollection.Add(new DummyClass() { Name = "Item1", Number = 0 });
DummyClassCollection.Add(new DummyClass() { Name = "Item2", Number = 1 });
DummyClassCollection.Add(new DummyClass() { Name = "Item3", Number = 2 });
The data table which to which the datagrid is bound...
DataTable table = new DataTable();
table.Columns.Add("DummyColumn", typeof(int));
table.Columns.Add("Bool", typeof(bool));
table.Rows.Add(1,true);
table.Rows.Add(2,false);
So comparing this example against yours, it seems perhaps that the problem is SelectedValue versus SelectedItem.
Again is the very hard to sort out your problem without knowing more about the structure of your bound data.

highlight combobox item if it's text starts with the text of the combobox's textbox

I want to highlight(make bold and change its color) all the items whose text starts with the text of the combobox's textbox.
I have tried to google the above question but I am unlucky to get any similar results which would solve my problem.
I think just a hint might be more than enough to solve this problem. Though I am a newbie. If it is possible give me a simple example.
Update :
Here is the code that I tried:
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
TextSearch.TextPath="GroupName" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="ComboBox_PART_Editable">
<Setter Property="Foreground" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</VirtualizingStackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But I dont know with what should I replace ComboBox_PART_Editable and I don't want the whole text I just want to use Text.StartsWith
I assume that the items in your ComboBox are just plain string values. You will have to change that and create a class to display each item. The reason for this is that you will need some bool 'flag' property that you can bind to a DataTrigger that will highlight your entries according to your requirement. So you could do this:
public class CustomComboBoxItem : INotifyPropertyChanged
{
public string Value { get; set; } // Implement INotifyPropertyChanged correctly...
public bool IsHighlighted { get; set; } // ... here, unlike this example
}
Then you'd need a collection property in your code behind or view model:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
Again, you must implement the INotifyPropertyChanged interface correctly here. Then you could bind it to the ComboBox.ItemsSource property like this:
<ComboBox ItemsSource="{Binding Items}" ... />
By now, this should look like a normal ComboBox with text entries, so we have to provide a DataTemplate to tell the entries to get highlighted when a condition is met... that's what the IsHighlighted property is for:
<DataTemplate DataType="{x:Type YourXmlNamespacePrefix:CustomComboBoxItem}">
<TextBlock Text="{Binding Value}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
The final piece of the puzzle is to set the IsHighlighted properties according to your requirements. For this, we'll need to bind to the ComboBox.Text property so that we know what that value is in the code. For this, add another property next to the collection property and update the item's IsHighlighted properties inside whenever it changes:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
public string InputValue
{
get { return inputValue; }
set
{
inputValue = value;
NotifyPropertyChanged("Items");
for (int i = 0; i < Items.Count; i++)
{
Items[i].IsHighlighted = Items[i].StartsWith(inputValue);
}
}
}
...
<ComboBox ItemsSource="{Binding Items}" Text="{Binding InputValue}" ... />
Well that was a bit more thorough than I had intended, but there you go. Let me know how you get on.

Listview item text truncated after itemssource change

I have WPF ListView and MVVM. ListView is a part of simple parent-child structure. Parent is also ListView control. When I change selected item in parent control, ItemsSource for child control is updated. If, for example, in first ItemsSource the longest text of the items contains 5 characters, after changing ItemsSource, every item's text is visible up to 5 characters (in new ItemsSource). How can I override this problem?
Code example:
<ListView Grid.Row="0" Grid.Column="3" ItemsSource="{Binding Tasks}" Width="200" Height="250" VerticalAlignment="Bottom" SelectionMode="Extended">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
I use BureauBlue theme but anyway, if I don't use it, I have the same problem.
Part of the code in ViewModel:
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentParentItem")
{
if (this.CurrentParentItem != null)
{
this.Tasks = GetTasks();//ObservableCollection
}
else
{
this.Tasks = null;
}
}
}
GridView does not recalculate width with a new data. To reset width you can use the following
foreach (GridViewColumn c in gv.Columns)
{
// Code below was found in GridViewColumnHeader.OnGripperDoubleClicked() event handler (using Reflector)
// i.e. it is the same code that is executed when the gripper is double clicked
// if (adjustAllColumns || App.StaticGabeLib.FieldDefsGrid[colNum].DispGrid)
if (adjustAllColumns || fdGridSorted[colNum].AppliedDispGrid)
{
if (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}

Change control in a DataGridRow programmatically

I have a WPF DataGrid contains a list of Products with an image button in the last column to add the Product item into the Orders_Products collection. That works.
Now I'd like to change the "add" image button with a "delete" image button if the Product item is already in the Orders_Products collection.
I tried to use the LoadingRow event but it seems there's no way to access the Image object because is not ready yet in the LoadingRow event...
I tried the Load event of the Image object, but it's not fired if the row is not visible in the form (when I must scroll the datagrid to see that row). It fires when I sort a column and the row is directly visible in the form. I'm going crazy... :(
I think I'm not doing something unusual but I'm new to WPF and probably I miss something.
Can someone give me an hint?
Thanks in advance,
Joe
P.S.: I'm using Entity Framework.
The proper way of doing this is by binding the DataGrid to the collection of objects you want to display (you are probably doing this already).
Then, manually define a DataGridTemplateColumn and set its CellTemplate to an appropriate DataTemplate (this would usually be defined as a resource in your DataGrid or somewhere higher in the logical tree of elements).
You can see an example of how the above is done here.
Inside the DataTemplate, use the technique described in my answer to this question to vary what is displayed in the template by matching the value of an appropriate property in the databound Product.
All of this can be done entirely in XAML, as is the preferred way of doing things in WPF.
Update (working example)
The Product:
public class Product
{
public string Name { get; set; }
public bool Exists { get; set; }
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
this.Products = new List<Product>
{
new Product { Name = "Existing product", Exists = true },
new Product { Name = "This one does not exist", Exists = false },
};
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<Product> Products { get; set; }
}
MainWindow.xaml:
<Window x:Class="SandboxWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ButtonColumnTemplate" >
<ContentControl x:Name="MyContentControl" Content="{Binding}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="True">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Remove product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Add product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Add/Remove" CellTemplate="{StaticResource ButtonColumnTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

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