How to display too long text properly in WPF ComboBox - wpf

I have a ComboBox that shows text of various lengths. For texts that are not long there is not a problem. For the texts longer than the width of ComboBox I would like to trim the text and add "..." (an ellipsis) at the end to show them properly. The bottom line is that I don't want to change the width of the ComboBox. Does anyone know how to do this?

Use a custom ItemTemplate for your ComboBox, which makes use of a TextBlock with the TextTrimming property set to CharacterEllipsis.
Example:
<ComboBox ItemsSource="..." SelectedValuePath="...">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ...}"
TextTrimming="CharacterEllipsis" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

The answer, as Ross said, is to implement a custom ItemTemplate. However, to make it work properly, you need to do the binding properly.
A note on this method: You cannot set both the DisplayMemberPath and the ItemTemplate, it must be one or the other.
So, for the general case where the display member is the item (such as for a string), you can use binding with no properties to bind to the DataContext of the template:
<ComboBox ItemsSource="..." SelectedValuePath="...">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Or, you can put it in a style.
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding }" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
For the case where you want to bind to a specific property of the object, similar to how you would use the DisplayMemberPath property, replace the binding with the binding that you would use to a property on the object that you are binding. So, replace the fourth line in my first example with something like this:
<TextBlock Text="{Binding MyDisplayMemberProperty}" TextTrimming="CharacterEllipsis" />
The binding is in the context of a single item of the type bound to your ComboBox. To make this more explicit, you can do the following:
<DataTemplate DataType="{x:Type namespace:MyItemType}">
<!-- My DataTemplate stuff here -->
</DataTemplate>
This will give you hints for the properties on the object while you are writing code inside the DataTemplate.

You can use TextTrimming CharacterEllipsis or WordEllipsis for the textblocks in your combobox.

Also works with a more complex DataTemplate; however, I had to resort to a DockPanel instead of the standard WrapPanel.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<AccessText DockPanel.Dock="Left" Text="{Binding Icon}"/>
<TextBlock Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
</DockPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

Hide the group header of a listview by binding in Windows Store App

I'm trying to remove group headers for groups where the header title is empty. But I can not make the binding in HeaderContainerStyle work. Neither can I set visibility on the TextBlock in TemplateHeader 'cause that will leave a small space and not be completely invisible.
This is my XAML:
<Page.Resources>
<CollectionViewSource
x:Name="MenuItemsGrouped"
IsSourceGrouped="True"
Source="{Binding MenuItems}" />
</Page.Resources>
<ListView Grid.Row="1" Margin="0"
ItemsSource="{Binding Source={StaticResource MenuItemsGrouped}}"
IsSynchronizedWithCurrentItem="False"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.HeaderContainerStyle>
<Style TargetType="ListViewHeaderItem">
<Setter Property="Visibility" Value="{Binding GroupHeaderVisibility}"></Setter>
</Style>
</GroupStyle.HeaderContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0" Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Does anyone have a solution - and maybe a reason the binding won't work?
EDIT:
Ok, it's is actually a limitation in Windows Store Apps and earlier Silverlight apps:
Windows Presentation Foundation (WPF) and Microsoft Silverlight
supported the ability to use a Binding expression to supply the Value
for a Setter in a Style. The Windows Runtime doesn't support a Binding
usage for Setter.Value (the Binding won't evaluate and the Setter has
no effect, you won't get errors, but you won't get the desired result
either). When you convert XAML styles from WPF or Silverlight XAML,
replace any Binding expression usages with strings or objects that set
values, or refactor the values as shared StaticResource values rather
than Binding-obtained values.
from http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.setter
And see also Silverlight: How to use a binding in setter for a style (or an equivalent work around)
Just try to bind the Visibility of the root element of HeaderTemplate.
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" Visibility = "{Binding GroupHeaderVisibility}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
It should work.Good luck!
[Edit]
I have figured out a solution, it's not very elegant, but it works. Here are the steps:
Add this xaml code to your ListView:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
2.Get a copy of ContainerStyle of GroupStyle(Right click the Listview on the design surface.Select: "Edit GroupStyle"->"ContainerStyle"). Then remove this line from the attributes of ContentControl:
Margin = "4"
3.Apply the above ContainerStyle to your listview. It should work.
NOTE: Step 1 is necessary, because ContainerStyle is no longer honored on Windows 8.1 when ItemsPanel is an ItemsStackPanel(which is the default).
http://msdn.microsoft.com/en-us/library/windows/apps/dn263110.aspx
You are binding to the Visibility property which is of type Visibility. My guess is that the GroupHasHeader property is a boolean; you should use a Boolean to visibility converter.

Binding in listbox with textblocks not working

I have the following xaml code:
<ListBox Foreground="{Binding MyColor, Converter={local:ColorConverter}}" ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
</ListBox>
This changes the foreground color for the entire listbox, so I modified the code in this way:
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding MyColor, Converter={local:ColorConverter}}" Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this way I wanted to set the foreground for an item instead for the entire listbox, but it is not working. How do I find the right datacontext ? MyColor is a property on my MainViewModel.
LATER EDIT WITH THE SOLUTION
Jens's answer was the one that showed me where I was wrong. Instead of storing simple message log strings in the ObservableCollection, I created a new class (LogItems) which contains a Message and a Color members. Now the LogCollection is typeof LogItems instead of strings.
I populate the listbox with the following code in my viewmodel:
LogItems logitem = new LogItems(myMessage, myColor);
LogCollection.Insert(0, logitem);
And the view has the following form. Also it doesn't require anymore to use RelativeSource, because the datacontext is the same.
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding Path=Color, Converter={local:ColorConverter}}" Text="{Binding Path=Message}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you all for your answers which lead me to this solution.
The DataContext of generated container in a listbox is automatically set to the corresponding item, therefore your Binding does not find the Property MyColor. You need to use a RelativeSource binding to bind to the DataContext of the containing list:
<TextBlock Foreground="{Binding DataContext.MyColor,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Converter={local:ColorConverter}}"
Text="{Binding}"/>

Element Data-Binding in Silverlight

I have a template column in a DataGrid:
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<TextBlock Text="{Binding Name,ElementName=rsAllSkills}"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
And in the same xaml file, I have
<riaControls:DomainDataSource QueryName="GetSkillsQuery" AutoLoad="True" x:Name="rsAllSkills">
<riaControls:DomainDataSource.DomainContext>
<domain:XXXX context/>
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
The DataSource has loaded everything successfully for sure, if I put that TextBlock out side of the DataGrid, it works; but inside the DataGrid, it doesn't load even the Name of rsAllSkills....
Could anybody give me a hint, thank you so much.
Have a dummy converter and check the binding.
What I guess is, the DataTemplate inside the CellEditingTemplate would receive the parent's DataContext, ie., DataGrid's DataContext. So, to work around this you can do one thing.
1) Bind the rsAllSkills to the Tag Property of DataGridTemplateColumn.
2) Now, Bind the TextBlock's Text property with the Tag property like,
<sdk:DataGridTemplateColumn Tag="{Binding Name,ElementName=rsAllSkills}">
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<TextBlock Text="{Binding Tag}"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>

WPF - Very basic ListBox.ItemTemplate Question

Ok, this is an embarassingly simple-looking problem, but is driving me crazy. I'm learning about DataTemplating and am trying to apply a very VERY simple ItemTemplate to a ListBox.
However, when I run my app, the template is completely ignored and I just get the standard-looking listbox, whereas in fact I'd expect to see a list of checkboxes with 'Test' along side.
I've tried this several times and always the same result. I've checked several resource on Google and all have the same kind of syntax for defining and ItemTemplate on a ListBox, so I really cannot see where I'm going wrong.
Code...
<Grid x:Name="LayoutRoot">
<ListBox x:Name="TestList"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="Check this checkbox!"/>
<TextBlock>Test</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<ListBoxItem>Bob</ListBoxItem>
<ListBoxItem>Jim</ListBoxItem>
<ListBoxItem>Dave</ListBoxItem>
<ListBoxItem>Larry</ListBoxItem>
<ListBoxItem>Tom</ListBoxItem>
</ListBox.Items>
</ListBox>
</Grid>
Any help greatly appreciated. Sorry for such a dumb-seeming question, but I've really fallen at the first hurdle here :(
AT
ItemTemplate wont work when you put ListBoxItem directly as items. General concept is you databind a CRL collection to the ListBox.ItemsSource and then specify the ItemTemplate. Check the below code.
<Grid x:Name="LayoutRoot">
<ListBox x:Name="TestList" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="Check this checkbox!"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<sys:String>Bob</sys:String>
<sys:String>Jim</sys:String>
<sys:String>Dave</sys:String>
<sys:String>Larry</sys:String>
<sys:String>Tom</sys:String>
</ListBox.Items>
</ListBox>
</Grid>
where sys is xmlns:sys="clr-namespace:System;assembly=mscorlib"
In this way, there are 5 ListBoxItems getting generated in the background and added to the ListBox.
You can use ItemContainerStyle instead of ItemTemplate if you want to add ListBoxItems directly to the ListBox.
Doing so, however, is only recommended when you need unique characteristics on a per item level.
If you are planning on all the items looking the same or making a dynamic list using ItemsSource, I would recommend you add strings (or another custom object) to your list and use ItemTemplate to display your items. (see Jobi Joy's answer)
Here's an example using ItemContainerStyle:
<ListBox
x:Name="TestList"
SelectionMode="Multiple">
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="ListBoxItem">
<StackPanel>
<CheckBox
Content="Check this checkbox!" />
<TextBlock
Text="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Items>
<ListBoxItem>Bob</ListBoxItem>
<ListBoxItem>Jim</ListBoxItem>
<ListBoxItem>Dave</ListBoxItem>
<ListBoxItem>Larry</ListBoxItem>
<ListBoxItem>Tom</ListBoxItem>
</ListBox.Items>
</ListBox>
For some reason DataTemplate can still be ignored if the ListBox is populated using ItemsSource e.g:
<ListBox Name="Test" x:FieldModifier="public" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that this is bound to an ObservableCollection containing objects (TextAdapter : INotifyPropertyChanged) with one property: string Text {...}

Force TextBlock to wrap in WPF ListBox

I have a WPF listbox which displays messages. It contains an avatar on the left side and the username and message stacked vertically to the right of the avatar. The layout is fine until the message text should word wrap, but instead I get a horizontal scroll bar on the listbox.
I've Googled and found solutions to similar issues, but none of them worked.
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Path=FriendsTimeline}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderBrush="DarkBlue" BorderThickness="3" CornerRadius="2" Margin="3" >
<Image Height="32" Width="32" Source="{Binding Path=User.ProfileImageUrl}"/>
</Border>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=User.UserName}"/>
<TextBlock Text="{Binding Path=Text}" TextWrapping="WrapWithOverflow"/> <!-- This is the textblock I'm having issues with. -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Contents of the TextBlock can be wrapped using property TextWrapping.
Instead of StackPanel, use DockPanel/Grid.
One more thing - set ScrollViewer.HorizontalScrollBarVisibility property to Disabled value for the ListBox.
Updated Hidden to Disabled based on comment from Matt. Thanks Matt.
The problem might not be located in the ListBox. The TextBlock won't wrap, if one of the parent controls provides enough space, so that it hasn't the need to wrap. This might be caused by a ScrollViewer control.
If you want to prevent TextBlock to grow, and you want it to just fit in the size of the listbox, you should set the width of it explicitly.
In order to change it dynamically, it means not a fix value, but you need to bind it to its proper parent element in the visual tree. You can have something like this:
<ListBox ItemsSource="{Binding MyItems}" Name="MyListBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Width"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}, Path=ActualWidth}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If it does not work, try to find the proper elements (which has to be binded to what) with the Live Visual Tree in Visual Studio.

Resources