Multiselect WPF combo box - wpf

I have a WPF combo box which ItemsSource is ObservableCollection<User>, where User has string Name and bool IsChecked.
Also I have
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}" Width="20" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
, which nicely shows check boxes before each name and allows me to check/uncheck users.
What I need is to make combo box selected item to show not the selected user but all checked usernames separated by comma, ideally (if resultant string is too long) with ellipsis in the middle, i. e. "Alice, Bart...mew, John".
Possible?

A little bit tricky to implement, but certainly can be done.
First of all you'll want to replace the drop-down bar/button with a TextBlock, so you'll need to modify the control's template. Place the edit cursor anywhere inside the parent ComboBox declaration so that the control appears in the Properties panel at the bottom right. In properties, find Miscellaneous -> Template at the bottom, then click on the little down-arrow to the right and select "Convert to new resource". This will template out the control so that you can start editing it.
Next, find the ToggleButton's ContentPresenter inside the ControlTemplate. You'll want to change it's Content binding to point to a property in your view models which I'll call Names:
<!--<ContentPresenter x:Name="contentPresenter" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" Content="{TemplateBinding SelectionBoxItem}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>-->
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" Content="{Binding Names}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
The next change is to also add a binding to your CheckBox Command property called "NamesChangedCommand" which will be called whenever the user changes the state of a CheckBox:
<CheckBox IsChecked="{Binding IsChecked}" Width="20" Command="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.NamesChangedCommand}" />
Then back in your view model, all you have to do is implement this command and have it generate the new name list. Here's how you'd do that in MVVM Toolkit:
[RelayCommand]
public void NamesChanged()
{
this.Names = String.Join(", ",
this.Items
.Where(item => item.IsChecked)
.Select(item => item.Name));
}
[ObservableProperty]
private string names = "";
Result:

I was going to suggest the xceed checkcombobox
https://github.com/xceedsoftware/wpftoolkit/wiki/CheckComboBox
But I notice it's not free for commercial use any more. You could build something of your own based on their code though.
https://github.com/xceedsoftware/wpftoolkit/blob/master/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/CheckComboBox/Implementation/CheckComboBox.cs
The xaml will be in that repo as well.

Related

XAML Property Element Not Found in Parent, what does it mean?

I have been drilling through XAML and trying to puzzle together how it actually works.
I have got the following XAML from one of the sample code I downloaded, an earlier question has explained away a large part of my confusion, however... I am still trying to make sense why the following element has a child element that references another type altogether.
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<VisualStateManager.VisualStateGroups>
<!-- Snipped code irrelevant - just various storyboards-->
</VisualStateManager.VisualStateGroups>
<Grid x:Name="InnerGrid"
Opacity="1"
Margin="0,5,0,5"
Background="{StaticResource TransparentColor}">
<ContentPresenter x:Name="ContentPresenter"
Foreground="{StaticResource TransparentColor}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</Border>
Looking at the XAML syntax documentation, the VisualStateManager is a PropertyElement, however I am confused because VisualStateGroups do not appear to be a property of Border. I believe the example is correct however, I need someone to explain to me, how is an element that's not a "proper" Child element (as that's what Grid is), be a legit part of the parent element?
VisualStateManager.VisualStateGroups is attached property. Read the following topic, it may help you: http://msdn.microsoft.com/en-us/library/ms749011.aspx

Adding Unselectable controls to RibbonMenuButton

I am working on a WPF project and I started developing the Ribbon area. I just put a RibbonMenuButton and added three RibbonTextBox inside of it. I want these TextBoxes to retrieve some data from the user. Everything is fine so far.
<rb:RibbonMenuButton LargeImageSource="/image.png" Label="Settings" >
<rb:RibbonTextBox Label="Field 01:" Text="{Binding Field01 }" />
<rb:RibbonTextBox Label="Field 02:" Text="{Binding Field02 }" />
<rb:RibbonTextBox Label="Field 03:" Text="{Binding Field03 }" />
</rb:RibbonMenuButton>
My problem is that the RibbonTextBox becomes a menu item, i.e. I can click it and select it.
But I want to avoid this behavior, I just want to have an "unselectable" RibbonTextBox.
Is there a way to achieve that?
Thank you in advance.
I found the solution here:
<Style TargetType="{x:Type rb:RibbonMenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type rb:RibbonMenuItem}">
<ContentPresenter ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}" Grid.Column="1"
ContentStringFormat="{TemplateBinding HeaderStringFormat}"
ContentSource="Header"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
VerticalAlignment="Center"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This works pretty well for me.
Those are typically called Labels. But in lieu of using the appropriate control, the RibbonTextBox has both a IsReadOnly propery, which will leave it selectable (ie. for copy/paste) but not editable. Or IsEnabled which will make it completely non-interactive.
I think the IsEnabled will also prevent you from being able to click on it at all, but I'm not 100% sure on that.

How to make a plain text-only Silverlight button?

I simply want a button with no background or anything other than plain text. I have done the following and the button does not show up at all:
<UserControl.Resources>
<ControlTemplate x:Key="linkButtons" TargetType="Button">
<TextBlock Foreground="White" FontSize="28" FontFamily="Verdana" Padding="10"></TextBlock>
</ControlTemplate>
</UserControl.Resources>
<Button Template="{StaticResource linkButtons}" Content="Hello World!"/>
This is because the TextBlock inside the Control template does not have a template binding. Make an attribute like this:
<TextBlock Foreground="White" FontSize="28" FontFamily="Verdana" Padding="10" Text="{TemplateBinding Content}" />
Not sure if thats the correct syntax, but thats the concept.
The problem is that a button is designed to have content, not text - it's a kind of ContentControl. So, to display the content, your template should have this in it:
<ContentPresenter x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
OR, you could make a custom control based on button, add a Text property to it, use your current TextBlock control in the template (but with Text="{Binding Text}") and leave the ContentPresenter out of your template. Making a custom control is a little trickier than just making a template for an existing one, but it's really the best way to get exactly what you're going for.

What is the difference between ContentTemplate and Content of ContentPresenter in ComboBoxItem related ControlTemplate

I am not knowing what to use to override an existing template (either ContentTemplate, Content). Imagine I have the following:
<Style x:Key="ComboBoxItemStyle1" TargetType="ComboBoxItem">
.
.
<ControlTemplate TargetType="ComboBoxItem">
.
.
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"/>
What is the difference between "ContentTemplate" and "Content" in the above markup (also, what is good for what).
Also, where can I find more information on "TemplateBinding"?
thanks
Content is generally used for your data. It will be set to the DataContext of the your ContentTemplate DataTemplate. The benefit of Content vs. ContentTemplate is the ability to change the ContentTemplate to display the same content in a different fashion.
Template Binding

Radiobuttons and Silverlight

I have radiobutton in Silverlight 3 with FontSize=20.
Within the control the radio dialog image forced to the top alignment.
Relative to the text/content the image appears too high. This is not obvious with smaller text sizes however with the large text size the vertical alignment between the text/content and the dialog image looks ugly.
Any ideas on how to vertical-align = middle both the text and the dialog image?
ty
The image inside the RadioButton consists of several Ellipse elements grouped into a Grid element. If you take a look at the RadioButton template (use Expression Blend to edit a copy of the existing template) you will see that the Grid elements VerticalAlignment is set to Top.
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse x:Name="Background" Fill="#FFFFFFFF"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"
Height="14" Margin="1" Width="14"/>
...
The ContentPresenters (where your text is displayed) VerticalAlignment is set to the value you set in the VerticalContentAlignment property.
<ContentPresenter x:Name="contentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Grid.Column="1" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
To fix your problem you need to set the VerticalAlignment of the Grid element to Middle or use TemplateBinding to set it to the VerticalContentAlignment value.
<Grid HorizontalAlignment="Left"
VerticalAlignment="Middle">
OR
<Grid HorizontalAlignment="Left"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
It appears the best way is to use something like Padding="5,-7,0,0"

Resources