Binding a listbox SelectedItem to an Observable Collection? - wpf

I have a Listbox in WPF with the SelectionMode set to Multiple, and can multiselect the items in the Listbox. However, the SelectedItem is not updating the Observable Collection it is bound to.
Is there a way to bind the multiple selected items from a ListBox to an Observable Collection?

i dont know mvvm way of doing this,
i have a working solution comibined of mvvm & codebehind.
CodeBehind
private void lstbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox == null) return;
var viewModel = listBox.DataContext as Window1ViewModel;
if (viewModel == null) return;
viewModel.ListOfSelectedItems.Clear();
foreach (Window1ViewModel.States item in listBox.SelectedItems)
{
viewModel.ListOfSelectedItems.Add(item);
}
}
ViewModel
private ObservableCollection<States> _listofselecteditems;
public ObservableCollection<States> ListOfSelectedItems
{
get
{
return _listofselecteditems;
}
set
{
_listofselecteditems = value;
RaisePropertyChanged(() => ListOfSelectedItems);
}
}
Xaml
<ListBox x:Name="lstbox"
SelectionChanged="lstbox_SelectionChanged_1"
ItemsSource="{Binding StatesList,Mode=TwoWay}"
SelectionMode="Multiple" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding Path=IsSelected,Mode=TwoWay}"
Content="{Binding StateName}" />
<TextBox Margin="8,0,0,0" Text="{Binding SOmeProperty}" IsEnabled="{Binding Path=IsSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>

Related

ItemContainerGenerator.ContainerFromItem method returns null if item has grouped

In WPF ListBox, I can get the selected item container using the method ItemContainerGenerator.ContainerFromItem(selectedItem) but it is not working when ListBoxItem is grouped.
MainWindow.xaml
<ListBox x:Name="listBox" ItemsSource="{Binding Contacts}" Loaded="cardView1_Loaded" SelectedIndex="0" Width="250" Height="250"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListBox.GroupStyle>
<GroupStyle/>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainWindow.xaml.cs
In the loaded method, First I have called this method ItemContainerGenerator.ContainerFromItem(selectedItem) and it returns the container of the selected item because the Listbox item is not grouped. Then I have added grouping for Listbox item. Now, if i called this method it returns null.
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void cardView1_Loaded(object sender, RoutedEventArgs e)
{
withOutGroup.Text = withOutGroup.Text + listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem);
ICollectionView collectionView = CollectionViewSource.GetDefaultView(listBox.ItemsSource);
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
withGroup.Text = withGroup.Text + listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem);
}
Sample: ListBox-Testing-Project
How can I get the selected item container if the Listbox item is grouped?
You need to wait to call the ContainerFromItem method until the container has actually been created. This works:
private void cardView1_Loaded(object sender, RoutedEventArgs e)
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(listBox.ItemsSource);
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded,
new Action(() =>
{
var container = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem);
//...
}));
}

Always select first item from listbox when binding is changed

There is a ListBox that its ItemsSource is bind to a collection,
we need always selected first item when binding is changed, for example at first ListBox has 3 items, second item is selected from ListBox by user, after that binding is changed and ListBox has 1 items, but second item is selected yet.( and also second item is emty but is not hidden)
<ListBoxItem x:Name="item1">
<Border Margin="0" >
<Image Source="{Binding Selected.List[2].Image,NotifyOnTargetUpdated=True}" ></Image>
</Border>
</ListBoxItem>
<ListBoxItem x:Name="item2">
<Border Margin="0">
<Image Source="{Binding Selected.List[1].Image,NotifyOnTargetUpdated=True}"></Image>
</Border>
</ListBoxItem>
<ListBoxItem x:Name="item3">
<Border Margin="0">
<Image Source="{Binding Selected.List[0].Image,NotifyOnTargetUpdated=True}"></Image>
</Border>
</ListBoxItem>
Can anyone help that how can resolve this issue?
You are adding items inside the xaml statically. Please create a collection property in your view model, bind it to the items source and bind the SelectedItem property of the listbox in the view model like this:
<ListBox ItemsSource="{Binding ItemCollection}"
SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- bind to item properties here -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Whenever the ItemCollection property is set, the view model could also set its SelectedItem property:
public class Item { ... }
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Item> itemCollection;
public IEnumerable<Item> ItemCollection
{
get { return itemCollection; }
set
{
itemCollection = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(ItemCollection)));
SelectedItem = itemCollection.FirstOrDefault(); // here
}
}
private Item selectedItem;
public Item SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedItem)));
}
}
}
first make an event for change binding and set the index in the change_event funciton
private void AddEventHandler()
{
myListBox.BindingContextChanged += new EventHandler(BindingContext_Changed);
}
private void BindingContext_Changed(object sender, EventArgs e)
{
myListBox.SelectedIndex = 0;
}
Firstly do you have any specific reason to add ListBoxItem in xaml itself.
If not please use observable collection to binding to Itemsource property of your listbox.
Also bind the selected item property of your ListBox to the property from your viewmodel.
Once your collection is changed set binded selected item property of your viewmodel to first item.
Also set,
IsSynchronizedWithCurrentItem="True" on your listbox in xaml.
This should work !

Access property of object in View from ViewModel

I have a TabControl in my View with multiple TabItems. I would like to change the its IsSelected property of one of the TabItems from my ViewModel.
Here is the xaml code for the View:
<TabControl Height="50" Margin="12,0,0,0">
<TabItem Name="tiCaptureSetup" >
<TabItem.Header>
<Button Name="btnCaptureSetup"
Grid.Column="0"
Width="90"
Height="40"
Margin="5"
ToolTip="Capture Setup"
Content="Capture Setup"
Click="btnCaptureSetup_Click"
IsEnabled="{Binding Path=CaptureSetupButtonStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsDefault="True"
></Button>
</TabItem.Header>
</TabItem>
Here is the C# code behind in View
private void btnCaptureSetup_Click(object sender, RoutedEventArgs e)
{
tiCaptureSetup.IsSelected = true; //select Capture Setup TabItem
MenuLSViewModel vm = (MenuLSViewModel)this.DataContext;
if (vm != null)
{
vm.CaptureSetupCommand.Execute(null);
}
}
And I would like to change tiCaptureSetup.IsSelected from ViewModel.
Any suggestions?
Simplest way: Make a property in your ViewModel called something like IsCaptureSetupSelected and bind it to the IsSelected property of tiCaptureSetup.
ViewModel:
private bool _IsCaptureSetupSelected;
public bool IsCaptureSetupSelected
{
get { return _IsCaptureSetupSelected; }
set
{
if (_IsCaptureSetupSelected != value)
{
_IsCaptureSetupSelected = value;
RaisePropertyChanged();
}
}
}
XAML:
<TabItem Name="tiCaptureSetup" IsSelected="{Binding IsCaptureSetupSelected}">
Note that I'm assuming you're using something like MVVMLight with your ViewModel...

how to know which treeview item is clicked using mvvm

I am having a WPF MVVM application which has a TreeView with all the static items maintained in XAML page. How do I know in my view-model which MenuItem is clicked so that I can show that respective page accordingly.
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"
></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
</TreeView>
I suppose that Selected event will have same effect as a click in your case. To determine which one TreeViewItem was selected you should add event Trigger:
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction
Command="{Binding selectItemCommand}"
CommandParameter="{Binding SelectedItem, ElementName=MyTreeViewMenu}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
As a result you can use and determine which item was selected by a parameter passed to Command.
ViewModel should look something like this:
private ICommand _selectItemCommand;
public ICommand selectItemCommand
{
get
{
return _selectItemCommand ?? (_selectItemCommand = new RelayCommand(param => this.LoadPage(param)));
}
}
private void LoadPage(object selectedMenuItem)
{
...
}
Take a look at the TreeView.SelectedItem Property page at MSDN.
You can bind directly to the TreeView.SelectedItem property:
<TreeView ItemsSource="{Binding Items}" SelectedItem="{Binding Item, Mode=OneWay}" />
Note that the TreeView.SelectedItem property is only read only, so you must use a OneWay binding... this means that you cannot set the selected item from your view model. To do that, you will need to create your own two way selected item property using an Attached Property.
EDIT >>>
My apologies #Scroog1, I normally use an AttachedProperty to do this. You are right that even with a OneWay binding, there is an error using this method. Unfortuately, my AttachedProperty code is long, but there is another way to do this.
I wouldn't necessarily recommend this as it's never really a good idea to put UI properties into your data objects, but if you add an IsSelected property to your data object, then you can bind it directly to the TreeViewItem.IsSelected property:
<TreeView ItemsSource="Items" HorizontalAlignment="Stretch" ... Name="MyTreeViewMenu">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I just searched and found a 'fuller' answer for you in the WPF MVVM TreeView SelectedItem post here on StackOverflow.
Alternatively, there is another way... you could also use the TreeView.SelectedValue and TreeView.SelectedValuePath properties. The basic idea is to set the TreeView.SelectedValuePath property to the name of a property on your data object. When an item is selected, the TreeView.SelectedValue property will then be set to the value of that property of the selected data item. You can find out more about this method from the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page at MSDN. This generally works best if you have a uniquely identifiable property like an identifier of some kind. This code example is from MSDN:
<TreeView ItemsSource="{Binding Source={StaticResource myEmployeeData},
XPath=EmployeeInfo}" Name="myTreeView" SelectedValuePath="EmployeeNumber" />
<TextBlock Margin="10">SelectedValuePath: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValuePath}" Foreground="Blue"/>
<TextBlock Margin="10">SelectedValue: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValue}" Foreground="Blue"/>
In addition to binding to the TreeView.SelectedItem property:
When using MVVM it helped me to stop thinking about events in the UI and start thinking about state in the UI.
You can bind the ViewModel to properties of the View. So in general I try to bind a SelectedItem to a property on the ViewModel so the ViewModel knows what is selected.
In the same way you could add a property to the ViewModel items being shown called Selected and bind this property to a checkbox in the View. That way you can enable multiple selection and access the selected items easily within the ViewModel.
For completeness, here are the attached property and TreeView subclass options:
Attached property option
public static class TreeViewSelectedItemHelper
{
public static readonly DependencyProperty BindableSelectedItemProperty
= DependencyProperty.RegisterAttached(
"BindableSelectedItem",
typeof (object),
typeof (TreeViewSelectedItemHelper),
new FrameworkPropertyMetadata(false,
OnSelectedItemPropertyChanged)
{
BindsTwoWayByDefault = true
});
public static object GetBindableSelectedItem(TreeView treeView)
{
return treeView.GetValue(BindableSelectedItemProperty);
}
public static void SetBindableSelectedItem(
TreeView treeView,
object selectedItem)
{
treeView.SetValue(BindableSelectedItemProperty, selectedItem);
}
private static void OnSelectedItemPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var treeView = sender as TreeView;
if (treeView == null) return;
SetBindableSelectedItem(treeView, args.NewValue);
treeView.SelectedItemChanged -= HandleSelectedItemChanged;
treeView.SelectedItemChanged += HandleSelectedItemChanged;
if (args.OldValue != args.NewValue)
SetSelected(treeView, args.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private static void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> args)
{
if (args.NewValue is TreeViewItem) return;
var treeView = sender as TreeView;
if (treeView == null) return;
var binding = BindingOperations.GetBindingExpression(treeView,
BindableSelectedItemProperty);
if (binding == null) return;
var propertyName = binding.ParentBinding.Path.Path;
var property = binding.DataItem.GetType().GetProperty(propertyName);
if (property != null)
property.SetValue(binding.DataItem, treeView.SelectedItem, null);
}
}
Subclass option
public class BindableTreeView : TreeView
{
public BindableTreeView()
{
SelectedItemChanged += HandleSelectedItemChanged;
}
public static readonly DependencyProperty BindableSelectedItemProperty =
DependencyProperty.Register(
"BindableSelectedItem",
typeof (object),
typeof (BindableTreeView),
new FrameworkPropertyMetadata(
default(object),
OnBindableSelectedItemChanged) {BindsTwoWayByDefault = true});
public object BindableSelectedItem
{
get { return GetValue(BindableSelectedItemProperty); }
set { SetValue(BindableSelectedItemProperty, value); }
}
private static void OnBindableSelectedItemChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
if (treeView != null) SetSelected(treeView, e.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> e)
{
SetValue(BindableSelectedItemProperty, SelectedItem);
}
}

Silverlight 4 and LisBox's SelectedItem when using DataTemplate as ItemTemplate

I'm using tow listboxes for drag n drop where user can drag items from source listbox and drop on the target. I'm using a DataTemplate for the ListBoxItems of my ListBox Controls.
I need to give the user ability to move up/down items in the target listbox after they been moved from source. I have two button "Move Up" & "Move Down" to do that but when the user clicks on one of the button, it returns me the null object as selectedItem.
here is my code
private void moveUp_Click(object sender, RoutedEventArgs e)
{
ListBoxItem selectedItem = lstmenuItems.SelectedItem as ListBoxItem;
if (selectedItem != null)
{
int index = lstmenuItems.Items.IndexOf(selectedItem);
if (index != 0)
{
lstmenuItems.Items.RemoveAt(index);
index -= 1;
lstmenuItems.Items.Insert(index, selectedItem);
lstmenuItems.SelectedIndex = index;
}
}
}
I'm sure its to do with the ItemTemplate. here is the xaml for listbox
<ListBox x:Name="lstmenuItems" Height="300" MinWidth="200" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Code}"/>
<TextBlock Text="{Binding RetailPrice, StringFormat=£\{0:n\}}" />
</StackPanel>
<!-- Product Title-->
<TextBlock Text="{Binding Description1}" Width="100" Margin="2" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Any ideas how can I access the selected Item and how can I move it up/down?
Thanks in advance
The selectedItem variable will contain a null because the SelectedItem property doesn't return the type ListBoxItem. The SelectedItem property returns an object it received from the collection supply its ItemsSource property.
Change to :-
object selectedItem = lstmenuItems.SelectedItem;
and that should get you a little further.
That said consider having the ItemsSource bound to an ObservableCollection and manipulate the collection instead.

Resources