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
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:
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With