MVVM - get selected items from ListBox via Data Binding - c#

I am writing app using MVVM pattern in C#.
My goal is to get selected items from ListBox in my own User Control.
I have created bindable object, with method to change this object (called when something new is selected):
public partial class MyUserControl : UserControl
{
...
public IEnumerable SelectedItems
{
get { return (IEnumerable)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IEnumerable),
typeof(MyUserControl),
new FrameworkPropertyMetadata(default(IEnumerable),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public MyUserControl ()
{
InitializeComponent();
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItems = ListBox.SelectedItems;
}
}
There is also Items part, and in xaml part ListBox is named ListBox:
<ListBox Name="ListBox" SelectionChanged="SelectionChanged" ... />
This how it looks in page with ViewModel, in which MyUserControl is created:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems}" />
And here comes the problem. When setting SelectedItems in ViewModel:
private ObservableCollection<MyObject> _myObjectItems;
public ObservableCollection<MyObject> MyObjectItems
{
get { return _myObjectItems; }
set { _myObjectItems = value; }
}
No matter what I do, value will always be null. This also means, that SelectedItems in MyUserControl is null, too.
I can, for example use OneWayToSource binding mode:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems, Mode=OneWayToSource}" />
value is still null, same as MyObjectItems, but at least SelectedItems in MyUserControl contains selected items. Not good enough :/

After hours of trying different approaches I've found NuGet package Extended WPF Toolkit. List SelectedItemsOverride from class CheckListBox allows to bind list of selected items:
<UserControl x:Class="View.UserControls.MyUserControl"
...
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
x:Name="Root">
<Grid>
<xctk:CheckListBox Name="ListBox"
ItemSelectionChanged="SelectionChanged"
SelectedItemsOverride="{Binding SelectedItems, ElementName=Root}"
... />
</Grid>
</UserControl>
And because of that, binding:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems}" />
works! I have access to selected items in View Model - everything in simple way.

Related

WPF DataGrid in textbox

I have a WPF DataGrid in XAML and C # and I want to select a row and display the row in the text box, it is not a DataGridView
x:Name="dtGConsultas" ItemsSource= "{Binding }"
HorizontalAlignment="Left" Margin="12,3,0,0" Grid.Row="6" VerticalAlignment="Top"
Grid.ColumnSpan="5" Height="111" Width="598" Grid.RowSpan="3"
SelectionChanged="dtGConsultas_SelectionChanged"/>
This can be done in several ways:
You can bind SelectedItem to some property and then display it
You can bind TextBox value to the DataGrid's SelectedItem
You can set the TextBox value on each call of SelectionChanged method
If You would be using MVVM pattern, You should pick 1st option.
Other 2nd an 3rd options are useful for You, but in bigger (complex) applications this solutions would cause issues to read the code easily & maintain it. Not recommended.
Examples:
MVVM approach
ViewModel file:
using using System.Collections.ObjectModel;
public class MyViewModel
{
//add implementation of INotifyPropertyChange & propfull
public ObservableCollection<MyItem> MySrcList { get; set; }
//add implementation of INotifyPropertyChange & propfull
public MyItem SelectedItem { get; set; }
}
View:
<UserControl ...
xmlns:local="clr-namespace:MyProject">
<UserControl.DataContext>
<local:MyProject />
</UserControl.DataContext>
...
<DataGrid
ItemsSource="{Binding MySrcList}"
SelectedItem="{Binding SelectedItem}"/>
Binding TB value to value of DataGrid's SelectedItem
Xaml file:
<Grid>
<DataGrid
x:Name="dtGConsultas"
ItemsSource="{Binding MySrcList}"/>
<TextBox Text="{Binding dtGConsultas.SelectedItem, Mode=OneWay}"/>
</Grid>
Code-behind (C# file):
public class MyUserControl
{
public MyUserControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<MyItem> MySrcList = new List<MyItem>();
}
Update in method (Code-behind):
Xaml file:
<Grid>
<DataGrid
x:Name="dtGConsultas"
ItemsSource="{Binding MySrcList}"
SelectionChanged="dtGConsultas_SelectionChanged"/>
<TextBox x:Name="MyTbx"/>
</Grid>
Code-Behind (C# file):
public class MyUserControl
{
public MyUserControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<MyItem> MySrcList = new List<MyItem>();
private void dtGConsultas_SelectionChanged( /* args */)
{
MyTbx.Text = dtGConsultas.SelectedItem.ToString();
}
}
You can also add a column that contains a checkbox and you bind it. Then juste check if (Your_List.element.CheckBox==true). you can get a list whit your checked elements

WPF ListBox Template bindings update only on scrolling

I have the following ListBox with the ContentControl as DataTemplate:
<ListBox x:Name="lstActionConfigs" ItemsSource="{Binding Path=AllActionConfigList}" SelectedItem="{Binding Path=ListSelectedItem, Mode=TwoWay}" HorizontalContentAlignment="Stretch" Grid.Row="3" Margin="0,0,0,5">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type helper:ItemDetails}">
<ContentControl Template="{StaticResource ResourceKey=actionDetailsListItemTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<i:Interaction.Behaviors>
<behaviours:BringIntoViewBehaviour CustomIsSelected="{Binding Path=IsSelected, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</ContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Each bounded instance has 'IsSelected' property which notify the UI on changes via INotifyPropertyChanged:
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
notify("IsSelected");
}
}
I built a custom behavior that brings into view the elements that changed it's IsSelectedProperty to true, as the follows:
public class BringIntoViewBehaviour : Behavior<FrameworkElement>
{
public bool CustomIsSelected
{
get { return (bool)GetValue(CustomIsSelectedProperty); }
set { SetValue(CustomIsSelectedProperty, value); }
}
public static readonly DependencyProperty CustomIsSelectedProperty =
DependencyProperty.Register("CustomIsSelected", typeof(bool), typeof(BringIntoViewBehaviour), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(customIsSelectedPropertyChanged_Callback)));
private static void customIsSelectedPropertyChanged_Callback(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
BringIntoViewBehaviour thisControl = o as BringIntoViewBehaviour;
if (thisControl == null)
return;
bringIntoView(thisControl);
}
}
This item is not presented this moment on the UI as it located at the bottom of the list (there is a scroll bar).
I updated the IsSelected property with true value.
However, the customIsSelectedPropertyChanged_Callback method should be executed as we updated it's bounded property.
But, in practice, this method is invoked only when this item is presented on UI when moving the scroll bar down to it.
The reason most likely is UI virtualization. ListBox items host is by default VirtualizingStackPanel. It will not generate items which are out of view now, so when you set IsSelected on your model, your DataTemplate together with your behaviour are not created yet. Only when you scroll down, control is created together with behaviour from data template, and after it is bound CustomIsSelectedProperty is set to true, so your callback is called.
To verify this assumption you can disable UI virtualization for your ListBox and see if that resolves the problem.

Data binding doesn't work with my dependency property

I'm developing a WPF application with .NET Framework 4 and MVVM Light Toolkit
I created a custom user control which only contains a DataGrid:
<UserControl
x:Class="PadacEtl.Matcher.Views.LaraDataGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding}">
<DataGrid ItemsSource="{Binding}" SelectionChanged="SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="Value" Binding="{Binding Model.Value}" />
</DataGrid.Columns>
</DataGrid>
</UserControl>
This control defines a dependency property SelectedItems:
public partial class CustomDataGrid : UserControl
{
public IEnumerable<ItemViewModel> SelectedItems
{
get { return (IEnumerable<ItemViewModel>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IEnumerable<ItemViewModel>),
typeof(CustomDataGrid), new PropertyMetadata(new List<ItemViewModel>()));
public CustomDataGrid()
{
InitializeComponent();
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dataGrid = sender as DataGrid;
SelectedItems = dataGrid.SelectedItems.Cast<ItemViewModel>();
}
}
Finally, this custom user control is used in a view, defined as follow:
<Window x:Class="Project.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:Project.Views"
Title="Project"
Height="700" Width="1050"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<Window.Resources>
<ResourceDictionary Source="Styles.xaml" />
</Window.Resources>
<Grid>
<uc:CustomDataGrid DataContext="{Binding Items}"
SelectedItems="{Binding SelectedItems}" />
</Grid>
</Window>
With the corresponding ViewModel:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
private IEnumerable<ItemViewModel> selectedItems = new List<ItemViewModel>();
public IEnumerable<ItemViewModel> SelectedItems
{
get { return selectedItems; }
set
{
if (value != selectedItems)
{
selectedItems = value;
RaisePropertyChanged(() => SelectedItems);
}
}
}
public MainViewModel()
{
//Something useful to feed Items
}
}
My problem is: When I select one or more rows from my CustomDataGrid, SelectedItems from MainViewModel is not updated. I think I didn't wired something well but I don't find what.
Any idea?
You have to have a two-way binding on your SelectedItems property. Either you do that explicitly in the binding expression like this:
<uc:CustomDataGrid ... SelectedItems="{Binding SelectedItems, Mode=TwoWay}"/>
or you set the FrameworkPropertyMetadataOptions.BindsTwoWayByDefault flags in the dependency property declaration:
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(
"SelectedItems",
typeof(IEnumerable<ItemViewModel>),
typeof(CustomDataGrid),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Note also that it is bad practice to set the default property value to a new collection instance, as that collection would be used as default value for all "instances" of the property. In other words, the default value of the SelectedItems property on two instances of CustomDataGrid would be the same collection object. If you add items to the one, the items would also be contained in the other. You have to set the default value in the constructor of the control.
Edit: After taking a second look at your UserControl and how you bind its properties, I realized that it can't work the way you designed it. Setting the DataContext as done in your binding declaration
<uc:CustomDataGrid DataContext="{Binding Items}"
SelectedItems="{Binding SelectedItems}"/>
would require to explicitly set the binding source object of the SelectedItems binding, perhaps like this
SelectedItems="{Binding SelectedItems, Source={StaticResource myViewModelInstance}}"
Instead of doing that, your control should have a bindable Items or ItemsSource in addition to the SelectedItems property. You could then simply write your bindings like this:
<uc:CustomDataGrid DataContext="{StaticResource myViewModelInstance}"
ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}"/>
Change the List to observablecollection, because observablecollection implements INotifyCollectionChanged and INotifyPropertyChanged where as list doesn't do so

WPF Binding Combox with different List and different SelectedValue

In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :
private string rockDensUnit;
public string RockDensity_Unit
{
get { return rockDensUnit; }
set
{
if (rockDensUnit != value)
{
rockDensUnit = value;
Changed("RockDensity_Unit");
}
}
}
In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.
ComboItems.cs
//**OBJECTS I USE FOR LIST OF IEMS**
// Class for kg, gm
public class KgGmItems
{
public ObservableCollection<string> KgGmList { get; set; }
public KgGmItems()
{
KgGmList = new ObservableCollection<string>();
KgGmList.Add("kg/m3");
KgGmList.Add("gram/cm3");
}
public string ValueSelected { get; set; } // Don't know if this is useful in my case
}
// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
public MtCmItems()
{
Dict = new Dictionary<string, string>
{
{"meter", "meter"},
{"centimeter", "centimeter"}
};
}
//...
}
XML i.e. ucStep2 View
<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
<ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
<ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.
In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.
I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.
By default the 1st item should be selected.
UPDATE
Added DependencyProperty in ucStep2.xaml.cs
public static readonly DependencyProperty RockDensityUnitProperty =
DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string RockDensity_Unit
{
get { return this.GetValue(RockDensityUnitProperty) as string; }
set { SetValue(RockDensityUnitProperty, value); }
}
XML
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
ERROR
Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", "
after FindAncestor
DOUBT
The RockDensity_Unit CLR property in Step2InfoData is untouched.
Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :
<UserControl x:Class="WellBore.ucStep2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WellBore.Models"
xmlns:top="clr-namespace:WellBore"
mc:Ignorable="d"
d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">
Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />
This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:
ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();
Or better like this if you had a uniquely identifiable property:
ValueSelected = KgGmList.Where(item => item.Id == Id).Single()
With your string values, you should be able to set the selected item from code like this:
ValueSelected = "Some value";
UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />
The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.
In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.
Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />
Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.
UPDATE 2 >>>
Your error is because you have to add an XML namespace at the top of your XAML file... something like this:
xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"
Then you use it to reference your class like this:
...AncestorType={x:Type YourNamespace:ucStep2} ...
Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change
Register("RockDensity_Unit", typeof(string), typeof(UserControl),
to
Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),
Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.
Use a Dictionary.
XAML
<ComboBox ItemsSource="{Binding Dict}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Prop}"/>
Code Behind
public Dictionary< ValueType, string > Dict { get; private set; }
private ValueType _prop;
public ValueType Prop
{
get{ return _prop }
set
{
_prop = value;
NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
}
}
public ViewModel()
{
Dict = new Dictionary< ValueType, string >()
{
{ value1, string1 },
{ value2, string2 },
{ value3, string3 }
};
}

How to support ListBox SelectedItems binding with MVVM in a navigable application

I am making a WPF application that is navigable via custom "Next" and "Back" buttons and commands (i.e. not using a NavigationWindow). On one screen, I have a ListBox that has to support multiple selections (using the Extended mode). I have a view model for this screen and store the selected items as a property, since they need to be maintained.
However, I am aware that the SelectedItems property of a ListBox is read-only. I have been trying to work around the issue using this solution here, but I have not been able to adopt it into my implementation. I found that I can't differentiate between when one or more elements are deselected and when I navigate between screens (NotifyCollectionChangedAction.Remove is raised in both cases, since technically all the selected items are deselected when navigating away from the screen). My navigation commands are located in a separate view model which manages the view models for each screen, so I can't put any implementation related to the view model with the ListBox in there.
I have found several other less elegant solutions, but none of these seem to enforce a two-way binding between the view model and the view.
Any help would be greatly appreciated. I can provide some of my source code if it would help to understand my problem.
Try creating an IsSelected property on each of your data items and binding ListBoxItem.IsSelected to that property
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
Rachel's solutions works great! But there is one problem I've encountered - if you override the style of ListBoxItem, you loose the original styling applied to it (in my case responsible for highlighting the selected item etc.). You can avoid this by inheriting from the original style:
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
Note setting BasedOn (see this answer)
.
I couldn't get Rachel's solution to work how I wanted it, but I found Sandesh's answer of creating a custom dependency property to work perfectly for me. I just had to write similar code for a ListBox:
public class ListBoxCustom : ListBox
{
public ListBoxCustom()
{
SelectionChanged += ListBoxCustom_SelectionChanged;
}
void ListBoxCustom_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = SelectedItems;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(nameof(SelectedItemsList), typeof(IList), typeof(ListBoxCustom), new PropertyMetadata(null));
}
In my View Model I just referenced that property to get my selected list.
I kept looking into an easy solution for this but with no luck.
The solution Rachel has is good if you already have the Selected property on the object within your ItemsSource. If you do not, you have to create a Model for that business model.
I went a different route. A quick one, but not perfect.
On your ListBox create an event for SelectionChanged.
<ListBox ItemsSource="{Binding SomeItemsSource}"
SelectionMode="Multiple"
SelectionChanged="lstBox_OnSelectionChanged" />
Now implement the event on the code behind of your XAML page.
private void lstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listSelectedItems = ((ListBox) sender).SelectedItems;
ViewModel.YourListThatNeedsBinding = listSelectedItems.Cast<ObjectType>().ToList();
}
Tada. Done.
This was done with the help of converting SelectedItemCollection to a List.
Here's yet another solution. It's similar to Ben's answer, but the binding works two ways. The trick is to update the ListBox's selected items when the bound data items change.
public class MultipleSelectionListBox : ListBox
{
public static readonly DependencyProperty BindableSelectedItemsProperty =
DependencyProperty.Register("BindableSelectedItems",
typeof(IEnumerable<string>), typeof(MultipleSelectionListBox),
new FrameworkPropertyMetadata(default(IEnumerable<string>),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));
public IEnumerable<string> BindableSelectedItems
{
get => (IEnumerable<string>)GetValue(BindableSelectedItemsProperty);
set => SetValue(BindableSelectedItemsProperty, value);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
BindableSelectedItems = SelectedItems.Cast<string>();
}
private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MultipleSelectionListBox listBox)
listBox.SetSelectedItems(listBox.BindableSelectedItems);
}
}
Unfortunately, I wasn't able to use IList as the BindableSelectedItems type. Doing so sent null to my view model's property, whose type is IEnumerable<string>.
Here's the XAML:
<v:MultipleSelectionListBox
ItemsSource="{Binding AllMyItems}"
BindableSelectedItems="{Binding MySelectedItems}"
SelectionMode="Multiple"
/>
There's one thing to watch out for. In my case, a ListBox may be removed from the view. For some reason, this causes the SelectedItems property to change to an empty list. This, in turn, causes the view model's property to be changed to an empty list. Depending on your use case, this may not be desirable.
This was pretty easy to do with a Command and the Interactivities EventTrigger. ItemsCount is just a bound property to use on your XAML, should you want to display the updated count.
XAML:
<ListBox ItemsSource="{Binding SomeItemsSource}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}"
CommandParameter="{Binding ElementName=MyView, Path=SelectedItems.Count}" />
</i:EventTrigger>
</Interaction.Triggers>
</ListView>
<Label Content="{Binding ItemsCount}" />
ViewModel:
private int _itemsCount;
private RelayCommand<int> _selectionChangedCommand;
public ICommand SelectionChangedCommand
{
get {
return _selectionChangedCommand ?? (_selectionChangedCommand =
new RelayCommand<int>((itemsCount) => { ItemsCount = itemsCount; }));
}
}
public int ItemsCount
{
get { return _itemsCount; }
set {
_itemsCount = value;
OnPropertyChanged("ItemsCount");
}
}
Turns out binding a check box to the IsSelected property and putting the textblock and checkbox within a stack panel does the trick!
Not satisfied with the given answers I was trying to find one by myself...
Well it turns out to be more like a hack then a solution but for me that works fine. This Solution uses MultiBindings in a special way.
First it may look like a ton of Code but you can reuse it with very little effort.
First I implemented a 'IMultiValueConverter'
public class SelectedItemsMerger : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SelectedItemsContainer sic = values[1] as SelectedItemsContainer;
if (sic != null)
sic.SelectedItems = values[0];
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { value };
}
}
And a SelectedItems Container/Wrapper:
public class SelectedItemsContainer
{
/// Nothing special here...
public object SelectedItems { get; set; }
}
Now we create the Binding for our ListBox.SelectedItem (Singular). Note: You have to create a static Resource for the 'Converter'. This may be done once per application and be reused for all ListBoxes that need the converter.
<ListBox.SelectedItem>
<MultiBinding Converter="{StaticResource SelectedItemsMerger}">
<Binding Mode="OneWay" RelativeSource="{RelativeSource Self}" Path="SelectedItems"/>
<Binding Path="SelectionContainer"/>
</MultiBinding>
</ListBox.SelectedItem>
In the ViewModel I created the Container where I can bind to. It is important to initialize it with new() in order to fill it with the values.
SelectedItemsContainer selectionContainer = new SelectedItemsContainer();
public SelectedItemsContainer SelectionContainer
{
get { return this.selectionContainer; }
set
{
if (this.selectionContainer != value)
{
this.selectionContainer = value;
this.OnPropertyChanged("SelectionContainer");
}
}
}
And that's it. Maybe someone sees some improvements?
What do You think about it?
This was a major issue for me, some of the answers I have seen were either too hackish, or required resetting the SelectedItems property value breaking any code attached to the properties OnCollectionChanged event. But I managed to get a workable solution by modifying the collection directly and as a bonus it even supports SelectedValuePath for object collections.
public class MultipleSelectionListBox : ListBox
{
internal bool processSelectionChanges = false;
public static readonly DependencyProperty BindableSelectedItemsProperty =
DependencyProperty.Register("BindableSelectedItems",
typeof(object), typeof(MultipleSelectionListBox),
new FrameworkPropertyMetadata(default(ICollection<object>),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));
public dynamic BindableSelectedItems
{
get => GetValue(BindableSelectedItemsProperty);
set => SetValue(BindableSelectedItemsProperty, value);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (BindableSelectedItems == null || !this.IsInitialized) return; //Handle pre initilized calls
if (e.AddedItems.Count > 0)
if (!string.IsNullOrWhiteSpace(SelectedValuePath))
{
foreach (var item in e.AddedItems)
if (!BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
BindableSelectedItems.Add((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
}
else
{
foreach (var item in e.AddedItems)
if (!BindableSelectedItems.Contains((dynamic)item))
BindableSelectedItems.Add((dynamic)item);
}
if (e.RemovedItems.Count > 0)
if (!string.IsNullOrWhiteSpace(SelectedValuePath))
{
foreach (var item in e.RemovedItems)
if (BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
BindableSelectedItems.Remove((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
}
else
{
foreach (var item in e.RemovedItems)
if (BindableSelectedItems.Contains((dynamic)item))
BindableSelectedItems.Remove((dynamic)item);
}
}
private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MultipleSelectionListBox listBox)
{
List<dynamic> newSelection = new List<dynamic>();
if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
foreach (var item in listBox.BindableSelectedItems)
{
foreach (var lbItem in listBox.Items)
{
var lbItemValue = lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null);
if ((dynamic)lbItemValue == (dynamic)item)
newSelection.Add(lbItem);
}
}
else
newSelection = listBox.BindableSelectedItems as List<dynamic>;
listBox.SetSelectedItems(newSelection);
}
}
}
Binding works just as you would have expected MS to have done themselves:
<uc:MultipleSelectionListBox
ItemsSource="{Binding Items}"
SelectionMode="Extended"
SelectedValuePath="id"
BindableSelectedItems="{Binding mySelection}"
/>
It has not been thoroughly tested but has passed first glance inspections. I tried to keep it reuseable by employing dynamic types on the collections.
It took me a while to implement binding/using SelectedItems as I am not an expert at this so I wanted to share my solution if someone might find it useful. Do not forget to download Microsoft.Xaml.Behaviors.Wpf from Nuget for this solution.
I have benefited from Accessing WPF ListBox SelectedItems
View:
Window x:Class="WpfAppSelectedItems.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:WpfAppSelectedItems"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox Height="250" Width="300"
ItemsSource="{Binding Items}" SelectionMode="Extended"
>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.InputBindings>
<KeyBinding Gesture="Ctrl+A" Command="{Binding SelectAllCommand}" />
</ListBox.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:CallMethodAction TargetObject="{Binding}" MethodName="ListBox_SelectionChanged"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</Window>
`
Code behind:
namespace WpfAppSelectedItems
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel(); //connecting window to VM
}
}
}
ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Xaml.Behaviors;
using System.Windows;
namespace WpfAppSelectedItems
{
internal class ViewModel: Presenter
{
//Creating ItemPresenter class. IsSelected binded to Style in the view
public class ItemPresenter : Presenter
{
private readonly string _value;
public ItemPresenter(string value)
{
_value = value;
}
public override string ToString()
{
return _value;
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
//Placing items to the Items which is binded to the ListBox
public ObservableCollection<ItemPresenter> Items { get; } = new ObservableCollection<ItemPresenter>
{
new ItemPresenter("A"),
new ItemPresenter("B"),
new ItemPresenter("C"),
new ItemPresenter("D")
};
//Do something when selection changed including detecting SelectedItems
public void ListBox_SelectionChanged()
{
foreach (var item in Items)
{
if (item.IsSelected)
MessageBox.Show(fufuitem.ToString());
}
}
};
//Notify View if a property changes
public abstract class Presenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
if you just want to get the Name of the selected Element you can do the following:
View:
<ListBox
x:Name="Folders"
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding YourListWithStings}"
SelectionMode="Single"
SelectedItem="{Binding ToYourOutputVariable}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Viewmodel:
private string _ToYourOutputVariable
public string ToYourOutputVariable
{
get {return _ToYourOutputVariable; }
set
{
_ToYourOutputVariable = value;
NotifyOfPropertyChange();
MessageBox.Show(_ToYourOutputVariable);
}
}
The messageBox shows the name of the selected listitem. You could call a function where you open the MessageBox

Categories