5

I've got a combobox and it's populated with a bunch of checkboxes. I wish the user to be able to click multiple times before the combobox closes (or is closed by the user themselves). The problems right now is that each time a checkbox is clicked, the combobox closes, forcing the user who wants to select multiple options to re-open it several times.

I've found other questions on the same subject but those apply to Silverlight, Qt etc. comparing the tags.

I've tried setting StayOpenOnEdit but that didn't do the trick. As far I could see, there's no property addressing my issue. That creates a suspicion that I might be barning up the wrong component all together.

  1. How do I prevent the combobox to close automatically after a click in a checkbox in it?
  2. Is there a more suitable component for such task and, if so, what's its name?

Please note that even if the answer to #2 is "yes", I'm still curious of #1 for purely academic reasons.

I've played with toggle button and list box, as someone suggested in a post. However, the that led only to an always fully shown list of all the checkboxes with some greyish thing behind it (which I'm assuming is the toggler). Perhaps I did something less clever in the mark-up.

<ToggleButton HorizontalAlignment="Left" 
              Margin="550,62,0,0" 
              VerticalAlignment="Top"
              Width="100">
  <ListBox x:Name="listBox1" 
             HorizontalAlignment="Left" 
             Height="100"
             VerticalAlignment="Top" Width="100">
    <CheckBox x:Name="checkBox3" Content="CheckBox"/>
    <CheckBox x:Name="checkBox4" Content="CheckBox"/>
  </ListBox>
</ToggleButton>

The point is to achieve something like this but it needs to be a standard WPF control (the concatenated line of all selected items is nice but not a must). Also, I've read the complaints that the binding and handling is not fully developed yet and I feel a bit suspicious.

Heena
  • 8,450
  • 1
  • 22
  • 40
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • This codeproject seems to cover your needs pretty well: http://www.codeproject.com/Articles/42133/Multiple-Selection-ComboBox-for-Silverlight (it's silverlight, but you can follow the same steps for wpf) – John Jan 03 '15 at 10:46
  • I've read it but there's still something confusing me. Is he working with checkboxes for both combobox and listbox? Or is he working with checkboxes placed in a listbox that itself is placed un a combobox? – Konrad Viltersten Jan 03 '15 at 14:52

2 Answers2

9

Using Exapander Control you can achieve multple item selection without closing popup after single selection.

For understanding please run this code separately.

xaml

  <Window.Resources>
    <ControlTemplate x:Key="ComboboxToggleButton" TargetType="{x:Type ToggleButton}">
        <Grid Background="{Binding Background,RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="35" />
            </Grid.ColumnDefinitions>
            <Border x:Name="Border" Grid.ColumnSpan="2" Background="Transparent"  BorderBrush="Black" BorderThickness="{Binding BorderThickness,RelativeSource={RelativeSource TemplatedParent}}"/>
            <Path x:Name="Arrow" Grid.Column="1" Opacity="1" Stroke="Black" StrokeThickness="1.5" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 6 6 L 12 0" />
            <Path x:Name="Arrow_checked" Opacity="0" Grid.Column="1" Fill="Black" Stroke="Black" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 6 6 L 12 0 Z" />
            <ContentPresenter TextElement.FontFamily="Segoe Ui Dark" TextElement.FontSize="18" TextElement.Foreground="Black" VerticalAlignment="Center"  Grid.Column="0" Margin="10,0,0,0" HorizontalAlignment="Left" RecognizesAccessKey="True" SnapsToDevicePixels="True" />
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="ToggleButton.IsMouseOver" Value="true">
                <Setter TargetName="Border" Property="Background" Value="Gray" />
                <Setter TargetName="Border" Property="BorderThickness" Value="1.2" />
            </Trigger>
            <Trigger Property="IsChecked" Value="False">
                <Setter Property="Opacity" Value="1" TargetName="Arrow"/>
                <Setter Property="Opacity" Value="0" TargetName="Arrow_checked"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="true">
                <Setter Property="Opacity" Value="0" TargetName="Arrow"/>
                <Setter Property="Opacity" Value="1" TargetName="Arrow_checked"/>
            </Trigger>
            <Trigger Property="IsEnabled" Value="False">
                <Setter TargetName="Border" Property="Background" Value="Gray" />
                <Setter TargetName="Border" Property="BorderBrush" Value="White" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style TargetType="{x:Type Expander}">
        <Setter Property="FontFamily" Value="Segoe Ui Dark"></Setter>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        <Setter Property="MaxHeight" Value="200"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <DockPanel>
                        <ToggleButton x:Name="HeaderSite" Height="35" Background="{TemplateBinding Background}" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="Black"   FontStyle="{TemplateBinding FontStyle}"   FontFamily="Segoe UI Dark" 
                     IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"   Template="{StaticResource ComboboxToggleButton}" />
                        <Border  BorderThickness="0,4.5,0,0" BorderBrush="Transparent">
                            <Border x:Name="bod" BorderBrush="Transparent" SnapsToDevicePixels="True" BorderThickness="1">
                                <ContentPresenter x:Name="ExpandSite"  Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"  Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            </Border>
                        </Border>
                    </DockPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="true">
                            <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
                            <Setter Property="BorderBrush" TargetName="bod" Value="Black"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="Gray"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="itemstyle"   TargetType="{x:Type ListBoxItem}">
        <Setter Property="SnapsToDevicePixels" Value="true" />
        <Setter Property="OverridesDefaultStyle"  Value="true" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Grid x:Name="Border"  Height="40"  SnapsToDevicePixels="true">
                        <Grid.Background>
                            <SolidColorBrush Color="Transparent" />
                        </Grid.Background>
                        <ContentPresenter Name="cmb_name" TextElement.FontFamily="Segoe Ui Dark" TextElement.FontSize="18" TextElement.Foreground="Black" Margin="10,0,0,0" VerticalAlignment="Center"></ContentPresenter>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="Gray"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="FocusVisualStyle">
            <Setter.Value>
                <Style TargetType="Control">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate>
                                <Border BorderBrush="{DynamicResource customBlueBrush}" BorderThickness="1" Margin="1,2,2,2" >
                                    <Rectangle Fill="{DynamicResource customBlueBrush}" Opacity="0.1"></Rectangle>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Setter.Value>
        </Setter>
    </Style>

    <Style  TargetType="CheckBox" >
        <Setter Property="SnapsToDevicePixels" Value="True"></Setter>
        <Setter Property="OverridesDefaultStyle" Value="True"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="CheckBox">
                    <Grid x:Name="ab" Background="Transparent">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="30"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Rectangle VerticalAlignment="Center" Height="20" Width="20"  Fill="White" HorizontalAlignment="Left"></Rectangle>
                        <Grid x:Name="checkGrid" VerticalAlignment="Center" Height="20" Width="20" Background="Black" HorizontalAlignment="Left">
                            <Viewbox Height="13" Width="13">
                                <Path x:Name="Check" SnapsToDevicePixels="True" UseLayoutRounding="True" Width="18.7969" Height="16.3094" Canvas.Left="0" Canvas.Top="1.52588e-005" Stretch="Fill" Fill="White" Data="F1 M 0.731262,8.75935L 0.106262,8.08437C 0.0354614,7.9948 0,7.8979 0,7.79375C 0,7.66875 0.0479736,7.5573 0.143799,7.45937L 1.94067,5.77187C 2.02606,5.69893 2.12708,5.66249 2.24377,5.66249C 2.30212,5.66249 2.36096,5.67397 2.42035,5.69685C 2.47974,5.71977 2.52814,5.75417 2.56567,5.79997L 7.5188,11.1406L 16.0438,0.165604C 16.1417,0.055191 16.2584,1.52588e-005 16.3938,1.52588e-005C 16.4979,1.52588e-005 16.5896,0.0322723 16.6688,0.0968475L 18.6313,1.60939C 18.6709,1.64272 18.7084,1.69011 18.7438,1.75154C 18.7792,1.813 18.7969,1.8698 18.7969,1.92189C 18.7969,2.03435 18.7646,2.1385 18.7,2.23439L 7.74377,16.3094L 0.731262,8.75935 Z " />
                            </Viewbox>
                        </Grid>
                        <Grid Background="Transparent" Grid.Column="1" IsHitTestVisible="True" HorizontalAlignment="Stretch">
                            <TextBlock VerticalAlignment="Center"  FontSize="18" FontFamily="Segoe Ui Dark" Foreground="Black"  TextTrimming="CharacterEllipsis">
                        <ContentPresenter></ContentPresenter>
                            </TextBlock>
                        </Grid>

                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Opacity" Value="1"  TargetName="Check"></Setter>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="False">
                            <Setter Property="Opacity" Value="0" TargetName="Check"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="FocusVisualStyle">
            <Setter.Value>
                <Style TargetType="Control">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate>
                                <Border BorderBrush="{DynamicResource customBlueBrush}" SnapsToDevicePixels="True" BorderThickness="1" Margin="-5,1,3,1" />
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<Grid x:Name="gd" >
    <Expander Width="500">
        <Expander.Header>
            <ListBox Background="Transparent" IsHitTestVisible="False" BorderBrush="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled"  BorderThickness="0" ItemsSource="{Binding ElementName=lst,Path=SelectedItems}">
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel  Orientation="Horizontal"></WrapPanel>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ContentData}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Expander.Header>
        <Expander.Content>
            <ListBox  Background="Transparent" ItemContainerStyle="{StaticResource itemstyle}" HorizontalContentAlignment="Stretch" x:Name="lst" SelectionMode="Multiple">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <CheckBox x:Name="checkBox"  IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Content="{Binding ContentData}"></CheckBox>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Expander.Content>
    </Expander>
</Grid>

c# code

  public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();          
        ObservableCollection<Customer> custdata = new ObservableCollection<Customer>();
        custdata.Add(new Customer() { ContentData = "content1" });
        custdata.Add(new Customer() { ContentData = "content2" });
        custdata.Add(new Customer() { ContentData = "content3" });
        custdata.Add(new Customer() { ContentData = "content4" });
        custdata.Add(new Customer() { ContentData = "content5" });
        custdata.Add(new Customer() { ContentData = "content6" });
        lst.ItemsSource = custdata;
    }
}
public class Customer
{
    public string ContentData { get; set; }
}

Result

enter image description here

Heena
  • 8,450
  • 1
  • 22
  • 40
  • Doesn't the expander control increase its own size when opened, instead of popping up without affecting the layout? – tomosius Jul 14 '15 at 14:37
8

Konrad,

I also decided to use a ComboBox in this manner, because the code was just dead simple. The easiest way I have found to keep the ComboBox popup open is to wire-up to the PreviewMouseDown event of the control in the ComboBox's item template. handle the behavior yourself, and then mark the mouse event handled. Works great for me. In the sample below, each object in FilterItems is a simple view model with a Text property and an IsChecked property.

<ComboBox IsEditable="True" IsReadOnly="False" ItemsSource="{Binding FilterItems}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}" 
                      PreviewMouseDown="FilterComboBox_PreviewMouseDown"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

And then my event handler is:

private void FilterComboBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    var cb = sender as CheckBox;
    if (cb != null)
    {
        cb.IsChecked = !cb.IsChecked;
        e.Handled = true;
    }
}
John V
  • 1,286
  • 9
  • 15