Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Listbox IsSynchronizedWithCurrentItem causes selection of the first item even though nothing its telling it to do it

I came across something that maybe a bug in wpf listbox. Please see the code and then I explain what happens

Window

<Window >
<Grid>
    <local:MultiSelectionComboBox Width="200"
                                  MinHeight="25"
                                  HorizontalAlignment="Center"
                                  VerticalAlignment="Center"
                                  ASLDisplayMemberPath="FirstName"
                                  ASLSelectedItems="{Binding SelectedModels}"
                                  ItemsSource="{Binding Models}" />
    <ListBox HorizontalAlignment="Right"
             VerticalAlignment="Top"
             DisplayMemberPath="FirstName"
             ItemsSource="{Binding SelectedModels}" />
</Grid>
</Window>

User control

<ComboBox>
<ComboBox.Style>
    <Style TargetType="{x:Type ComboBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBox}">
                    <Grid x:Name="MainGrid"
                          Width="Auto"
                          Height="Auto"
                          SnapsToDevicePixels="true">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="0" />
                        </Grid.ColumnDefinitions>
                        <Popup x:Name="PART_Popup"
                               Grid.ColumnSpan="2"
                               Margin="1"
                               AllowsTransparency="true"
                               IsOpen="{Binding Path=IsDropDownOpen,
                                                RelativeSource={RelativeSource TemplatedParent}}"
                               Placement="Center"
                               PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
                            <Border x:Name="DropDownBorder"
                                    MinWidth="{Binding Path=ActualWidth,
                                                       ElementName=MainGrid}"
                                    MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                <ScrollViewer CanContentScroll="true">
                                    <StackPanel>
                                        <CheckBox x:Name="checkBox" 
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=CheckAllCommand}">select all</CheckBox>
                                        <ListBox x:Name="lstBox"
here lies the problem without the line below I dont get to see the result on start up,
with the it selects the first item even if nothing is triggering it
                                                 IsSynchronizedWithCurrentItem="True"
                                                 ItemsSource="{TemplateBinding ItemsSource}"
                                                 KeyboardNavigation.DirectionalNavigation="Contained"
                                                 SelectionChanged="ListBoxOnSelectionChanged"
                                                 SelectionMode="Multiple"
                                                 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                    </StackPanel>
                                </ScrollViewer>
                            </Border>
                        </Popup>
                        <ToggleButton Grid.ColumnSpan="2"
                                      MinWidth="20"
                                      HorizontalAlignment="Right"
                                      Background="{TemplateBinding Background}"
                                      BorderBrush="{TemplateBinding BorderBrush}"
                                      IsChecked="{Binding Path=IsDropDownOpen,
                                                          Mode=TwoWay,
                                                          RelativeSource={RelativeSource TemplatedParent}}"
                                      Style="{DynamicResource ToggleButtonStyle1}" />
                        <ItemsControl x:Name="itemsControl"
                                      Margin="4,2,0,2"
                                      ItemsSource="{Binding Path=SelectedItems,
                                                            ElementName=lstBox}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel IsItemsHost="True" Orientation="Horizontal" />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Border Margin="1"
                                            HorizontalAlignment="Left"
                                            VerticalAlignment="Top"
                                            Background="#383838"
                                            CornerRadius="2"
                                            Padding="5,2,3,2">
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock x:Name="Test"
                                                       Foreground="White"
                                                       Loaded="Test_OnLoaded"
                                                       Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                                                                     AncestorType={x:Type ComboBox}},
                                                                      Path=ASLDisplayMemberPathActualValue}" />
                                            <Button Width="12"
                                                    Height="12"
                                                    Margin="5,0,0,0"
                                                    Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                                                                     AncestorType=ComboBox},
                                                                      Path=UnselectCommand}"
                                                    CommandParameter="{Binding}">
                                                <Button.Template>
                                                    <ControlTemplate TargetType="{x:Type Button}">
                                                        <Grid>
                                                            <Ellipse x:Name="PART_ButtonBackground"
                                                                     Fill="White"
                                                                     Opacity="0" />
                                                            <TextBlock x:Name="PART_Text"
                                                                       Margin="0,0,0,1"
                                                                       HorizontalAlignment="Center"
                                                                       VerticalAlignment="Center"
                                                                       FontSize="10"
                                                                       Foreground="White"
                                                                       Text="X" />
                                                        </Grid>
                                                        <ControlTemplate.Triggers>
                                                            <Trigger Property="IsMouseOver" Value="True">
                                                                <Setter TargetName="PART_ButtonBackground" Property="Opacity" Value="0.8" />
                                                                <Setter TargetName="PART_Text" Property="Foreground" Value="Black" />
                                                            </Trigger>
                                                        </ControlTemplate.Triggers>
                                                    </ControlTemplate>
                                                </Button.Template>
                                            </Button>
                                        </StackPanel>
                                    </Border>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ComboBox.Style>
</ComboBox>

code behind user control

public partial class MultiSelectionComboBox : ComboBox
{
    #region fields, dependencies, command and constructor
    private ListBox listBox;
    private ItemsControl itemsControl;
    private CheckBox checkBox;

    public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null));
    public static readonly DependencyProperty ASLDisplayMemberPathProperty = DependencyProperty.Register("ASLDisplayMemberPath", typeof(string), typeof(MultiSelectionComboBox), new PropertyMetadata("Description"));

    public MultiSelectionComboBox()
    {
        InitializeComponent();
    }

    public DelegateCommand<object> UnselectCommand { get; private set; }
    public DelegateCommand CheckAllCommand { get; private set; }

    public ObservableCollection<object> ASLSelectedItems
    {
        get { return (ObservableCollection<object>)GetValue(ASLSelectedItemsProperty); }
        set { SetValue(ASLSelectedItemsProperty, value); }
    }

    public string ASLDisplayMemberPath
    {
        get { return (string)GetValue(ASLDisplayMemberPathProperty); }
        set { SetValue(ASLDisplayMemberPathProperty, value); }
    }
    #endregion

    private void CheckAll()
    {
        if (checkBox.IsChecked == true)
        {
            listBox.SelectAll();

        }
        else
        {
            listBox.UnselectAll();
        }
    }

    private void GetControls()
    {
        checkBox = Template.FindName("checkBox", this) as CheckBox;
        listBox = Template.FindName("lstBox", this) as ListBox;
        itemsControl = Template.FindName("itemsControl", this) as ItemsControl;
    }

    private bool bug = true;
    private void ListBoxOnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        e.AddedItems.Cast<object>().Where(item => !ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Add(p));
        e.RemovedItems.Cast<object>().Where(item => ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Remove(p));
        checkBox.IsChecked = (listBox.ItemsSource as IList).Count == listBox.SelectedItems.Count;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        GetControls();
        SetControls();
        UnselectCommand = new DelegateCommand<object>(p => listBox?.SelectedItems.Remove(p));
        CheckAllCommand = new DelegateCommand(CheckAll);
    }

    private void SetControls()
    {
        listBox.DisplayMemberPath = ASLDisplayMemberPath;
        itemsControl.DisplayMemberPath = ASLDisplayMemberPath;
    }

    private void Test_OnLoaded(object sender, RoutedEventArgs e)
    {
        (sender as TextBlock)?.SetBinding(TextBlock.TextProperty, ASLDisplayMemberPath);
    }
}

view model

public class MainWindowViewModel
{
    public ObservableCollection<Model> Models { get; set; }
    public ObservableCollection<object> SelectedModels { get; set; }

    public MainWindowViewModel()
    {
        Models = new ObservableCollection<Model>()
        {
            new Model() {FirstName = "Amelia", Age = 0},
            new Model() {FirstName = "Bruno", Age = 5},
            new Model() {FirstName = "Colin", Age = 47},
            new Model() {FirstName = "Daniel", Age = 32},
            new Model() {FirstName = "Iza", Age = 28},
            new Model() {FirstName = "James", Age = 23},
            new Model() {FirstName = "Simon", Age = 23}
        };
        SelectedModels = new ObservableCollection<object> {Models.FirstOrDefault() };
    }
}

Now the problem is that if inside the user control (where the listbox is) if I don’t set synchronize to true, then it won’t see the first item on start-up, if I do set it, then something forces an add to the collection. And then even if inside selected children I don’t set a value, it still sets the value of the first child.

This is actually multiselect combobox, got so far and this one simple thing stopped me for half of the day. And I can't find what is causing it.

Any help would be appreciated

like image 665
adminSoftDK Avatar asked Oct 15 '15 16:10

adminSoftDK


1 Answers

This will help us on how to understand how listbox behaves when IsSynchronizedWithCurrentItem is set to true. Link

Given your requirement, this should be false.

Your LisBox.SelectionChanged event handler will be invoked every time you set the Values to ASLSelectedItems.

I think it will work by:

  1. Remove your handler from your ListBox (lstBox).
  2. Then modify your DP

    public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null, OnSelectedItemsChanged));

    private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // Add your logic from your handler }

like image 87
tgpdyk Avatar answered Nov 15 '22 12:11

tgpdyk