Extended selection mode, virtualization and IsSelected binding - c#

It seems in extended selection mode IsSelected binding is buggy. Looks like only last item from selection which become out of scope is handled properly.
Demonstration:
Items 0, 1, 2 and 98, 97, 96 are selected with Control. When selecting 94 (without Control!) selection counter should be 1, but you see 3 instead. Scrolling up reveals what only one (last) item of selection out of scope was unselected.
Below is mcve:
xaml:
<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
cs:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class Item : NotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; }
}
public string Text { get; set; }
}
public class ViewModel : NotifyPropertyChanged
{
public ObservableCollection<Item> Items { get; }
public ViewModel()
{
var list = new List<Item>();
for (int i = 0; i < 100; i++)
list.Add(new Item() { Text = i.ToString() });
Items = new ObservableCollection<Item>(list);
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Title = ((ViewModel)DataContext).Items.Count(item => item.IsSelected).ToString();
}
}
A quick fix is to disable list control (ListBox or ListView) virtualization:
VirtualizingStackPanel.IsVirtualizing="False"
Question: any idea how to fix it without disabling virtualization?

Well, this is expected behavior. Virtualization only creates visual containers (ListBoxItem) for visible items. In order for bindings to work, the container must exist in the first place, so only visible items are affected.
There are two obvious solutions:
Disable virtualization.
Use SelectionChanged event instead. You can get added and removed items from SelectionChangedEventArgs. Then all you need to do is perform a cast and set the IsSelected property accordingly (you don't need to iterate over Items). Ctrl+A will work as well, you just have to handle added items too (and remove the binding altogether):
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var removedItem in e.RemovedItems.Cast<Item>())
{
removedItem.IsSelected = false;
}
foreach (var addedItem in e.AddedItems.Cast<Item>())
{
addedItem.IsSelected = true;
}
Title = ((ViewModel) DataContext).Items.Count(item => item.IsSelected).ToString();
}

Related

WPF Listbox focus from viewmodel

I've stumbled upon the well-known problem with Listbox and focus. I'm setting ItemsSource from the viewmodel and at some point I need to reload them and set selection and focus to a specific item, say:
private readonly ObservableCollection<ItemViewModel> items;
private ItemViewModel selectedItem;
private void Process()
{
items.Clear();
for (int i = 0; i < 100; i++)
{
items.Add(new ItemViewModel(i));
}
var item = items.FirstOrDefault(i => i.Value == 25);
SelectedItem = item;
}
public ObservableCollection<ItemViewModel> Items { /* usual stuff */ }
public ItemViewModel SelectedItem { /* usual stuff */ }
Binding may look like:
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
After calling the method item gets selected, but does not receive focus.
I've read a lot on the Internet and on StackOverflow, but all answers I found involve manual filling of the listbox, not via binding from viewmodel. So the question is: how can I properly focus newly selected item in the presented scenario?
To add some context, I'm implementing a sidebar file browser:
I need keyboard navigation on the listbox below treeview.
Here is a solution that might work for you:
The control:
class FocusableListBox : ListBox
{
#region Dependency Proeprty
public static readonly DependencyProperty IsFocusedControlProperty = DependencyProperty.Register("IsFocusedControl", typeof(Boolean), typeof(FocusableListBox), new UIPropertyMetadata(false, OnIsFocusedChanged));
public Boolean IsFocusedControl
{
get { return (Boolean)GetValue(IsFocusedControlProperty); }
set { SetValue(IsFocusedControlProperty, value); }
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ListBox listBox = dependencyObject as ListBox;
listBox.Focus();
}
#endregion Dependency Proeprty
}
The ViewModel:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Boolean _IsFocused;
private String selectedItem;
public ObservableCollection<String> Items { get; private set; }
public String SelectedItem
{
get
{
return selectedItem;
}
set
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public Boolean IsFocused
{
get { return _IsFocused; }
set
{
_IsFocused = value;
RaisePropertyChanged("IsFocused");
}
}
public ViewModel()
{
Items = new ObservableCollection<string>();
Process();
}
private void Process()
{
Items.Clear();
for (int i = 0; i < 100; i++)
{
Items.Add(i.ToString());
}
ChangeFocusedElement("2");
}
public void ChangeFocusedElement(string newElement)
{
var item = Items.FirstOrDefault(i => i == newElement);
IsFocused = false;
SelectedItem = item;
IsFocused = true;
}
private void RaisePropertyChanged(String propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
The XAML:
<local:FocusableListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Width="200"
IsFocusedControl="{Binding IsFocused, Mode=TwoWay}"/>
The update call:
_viewModel.ChangeFocusedElement("10");
I ended up with the following code in control's codebehind:
public void FixListboxFocus()
{
if (lbFiles.SelectedItem != null)
{
lbFiles.ScrollIntoView(lbFiles.SelectedItem);
lbFiles.UpdateLayout();
var item = lbFiles.ItemContainerGenerator.ContainerFromItem(viewModel.SelectedFile);
if (item != null && item is ListBoxItem listBoxItem && !listBoxItem.IsFocused)
listBoxItem.Focus();
}
}
This method is available for calling from within viewModel, which calls it every time it sets the selection:
var file = files.FirstOrDefault(f => f.Path.Equals(subfolderName, StringComparison.OrdinalIgnoreCase));
if (file != null)
SelectedFile = file;
else
SelectedFile = files.FirstOrDefault();
access.FixListboxFocus();
The access is view passed to ViewModel via interface (to keep separation between presentation and logic). The relevant XAML part looks like following:
<ListBox x:Name="lbFiles" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" />

ListBox with DataTemplate recognize SelectedItem

I have a ListBox with a simple DataTemplate, a CheckBox, and a TextBox.
If the user checks a CheckBox I want to get this changed item, like the property SelectedItem of the ListBox.
How can I get the element from List2, which has changed?
MyListItem:
public class MyListItem2 : ReactiveObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
this.RaiseAndSetIfChanged(ref _name, value, "Name");
}
}
private bool _isMarked;
public bool IsMarked
{
get { return _isMarked; }
set
{
this.RaiseAndSetIfChanged(ref _isMarked, value, "IsMarked");
}
}
}
View:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataTemplate.Views.MainWindow"
xmlns:viewsmodels="clr-namespace:DataTemplate.ViewModels;assembly=DataTemplate"
xmlns:dt="clr-namespace:DataTemplate;assembly=DataTemplate"
Title="DataTemplate" Width="700">
<Window.DataContext>
<viewsmodels:MainWindowViewModel />
</Window.DataContext>
<Grid ColumnDefinitions="250">
<ListBox Grid.Column="1" Items="{Binding List2}">
<ListBox.ItemTemplate>
<DataTemplate DataType="dt:MyListItem2">
<Grid ColumnDefinitions="50*,50*">
<CheckBox Grid.Column="0" Content="Mark" IsChecked="{Binding IsMarked}"/>
<TextBox Grid.Column="1" Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
ViewModel:
public class MainWindowViewModel : ReactiveObject
{
public ObservableCollection<MyListItem2> List2 { get; set; }
public MainWindowViewModel()
{
List2 = new ObservableCollection<MyListItem2>();
Random rand = new Random();
for (int i = 0; i < rand.Next(1, 20); i++)
{
MyListItem2 mli = new MyListItem2();
mli.Name = "ListItem" + i;
mli.IsMarked = false;
mli.PropertyChanged += ItemChanged;
List2.Add(mli);
}
}
private void ItemChanged(object sender, PropertyChangedEventArgs e)
{
var item = sender as MyListItem2;
Console.WriteLine(string.Format("changed: {0} {1}", item.Name, item.IsMarked));
}
}
I can see two ways:
Since you are using MVVM, implement the INotifyPropertyChanged interface on the MyListItem2 class (Microsoft Reference on INotifyPropertyChanged implementation). Raise the property change event when the IsMarked value is set/changed, then wire into the PropertyChanged event handler of the item to determine when it is changed. . OR
If you have codebehidn, add a "Checked" and/or "Unchecked" event handler on the checkbox itself from the XAML. Shown below.
CheckBox Grid.Column="0" Content="Mark" IsChecked="{Binding IsMarked}"/>
Checked="IsMarked_Checked"
Codebehind
public void IsMarked_Checked(object sender, RoutedEventArgs e)
{
var ck = sender As Checkbox;
if (ck == null)
{
return;
}
// do whatever you need to here using the datacontext of the Checkbox
}
If you want to know when a check box is checked/unchecked by the user you will need to trigger on the event from the checkbox.
Use something like this:
private void MyCheckBox_Checked(object sender, RoutedEventArgs e)
{
//check IsChecked of MyCheckBox here
}
Try setting binding Mode:
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"

Getting Only one Selected Item from Listbox

I am working with Listbox in wpf.My problem is I am getting only one selected item from listbox.I want to get all the item which is selected but don't know how to implement ? please help!
Here is my code :
<ListBox ItemsSource="{Binding Markets}" BorderThickness="0" Background="{x:Null}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" Style="{DynamicResource CheckBoxStyle1}" Width="140" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is my viewmodel content:
private string _selectedItem;
public List<string> Markets { get; set;}
public SettingVM()
{
Markets = new List<string>();
Markets.Add("United Kingdom");
Markets.Add("Australia");
}
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
Set(() => SelectedItem, ref _selectedItem, value);
}
}
You can bind directly the SelectedItem listobx property to your view model but not the SelectedItems property.
I used a behavior to achieve this associated with a delegate command (included in the Prism framework)
Your object
class MyObject
{
}
The behavior
internal class MyObjectSelectionChangedToCommand
{
public static readonly DependencyProperty SelectionCommandProperty =
DependencyProperty.RegisterAttached("SelectionCommand", typeof(DelegateCommand<GridSelectionInfo<MyObject>>),
typeof(ResourceCardGridSelectionChangedToCommand), new PropertyMetadata(null, OnSelectionCommandChanged));
public static DelegateCommand<GridSelectionInfo<MyObject>> GetSelectionCommand(DependencyObject obj)
{
return (DelegateCommand<GridSelectionInfo<MyObject>>)obj.GetValue(SelectionCommandProperty);
}
public static void SetSelectionCommand(DependencyObject obj, DelegateCommand<GridSelectionInfo<MyObject>> value)
{
obj.SetValue(SelectionCommandProperty, value);
}
private static void dg_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var tsci = new GridSelectionInfo<MyObject>
{
Added = e.AddedItems.Cast<MyObject>().ToList(),
Removed = e.RemovedItems.Cast<MyObject>().ToList(),
Selected = ((ListBox)sender).SelectedItems.Cast<MyObject>().ToList()
};
var cmd = GetSelectionCommand((DependencyObject)sender);
cmd.Execute(tsci);
}
private static void OnSelectionCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dg = d as ListBox;
if (dg != null) dg.SelectionChanged += dg_SelectionChanged;
}
}
public class GridSelectionInfo<T>
{
public GridSelectionInfo()
{
Selected = new List<T>();
Added = new List<T>();
Removed = new List<T>();
}
public List<T> Added { get; set; }
public List<T> Removed { get; set; }
public List<T> Selected { get; set; }
public override string ToString()
{
return string.Format("Added: {0}, Removed: {1}, Selected: {2}", Added.Count, Removed.Count, Selected.ToFormattedString());
}
}
The XAML part where you bind the view model command to the behavior
<ListBox ItemsSource="{Binding MyCollection}"
resources:ResourceCardGridSelectionChangedToCommand.SelectionCommand="{Binding CmdObjectSelectionChanged}">
</ListBox>
Then, in you view model you just have to declare the command
public DelegateCommand<GridSelectionInfo<MyObject>> CmdObjectSelectionChanged { get; private set; }
And create it
CmdObjectSelectionChanged = new DelegateCommand<<GridSelectionInfo<MyObject>>(ExecuteSelect,CanExecuteSelect);
Thus, every time the selection changes in your listbox, you will receive all the info about selected items in the execute delegate wrapped in the GridSelectionInfo object;
maybe this help you
You can find them in ListBox.SelectedItems. use foreach
foreach (ListItem li in listBox1.SelectedItems)
{
// your code here
}
You use "SelectedItem="{Binding SelectedItem}">". Acording to MSDN "Gets or sets the first item in the current selection or returns null if the selection is empty "
And you need to bind SelectedItems to a list in your viewmodel.
In the ListBox controls, the SelectedItems property is read-only but you can add a behavior that do the job, see this answer : https://stackoverflow.com/a/8369532/1431524
I had the same problem and when I was binding to the SelectedItems the property was not notifying the model in some cases! I ended extending my entity with a non persisted property ("Selected") and then binded this property to the IsCheched property of the CheckBox.
In your case create another partial class file Market to extend the Market object, add a new bool property (e.g. Selected) and then at your checkbox bind this property:
<CheckBox Content="{Binding}" IsChecked="{Binding Selected}" Style="{DynamicResource CheckBoxStyle1}" Width="140" Margin="10"/>

WPF Binding in ComboBox with UserControl list

In two combobox A and B.
A's ItemsSource is Custom list. and B's ItemsSource is UserControl list.
When manually setting the SelectedItem, A combobox works well, but B combobox UI do not show the selected Item. (In debugging, SelectedItem's value mapping is right, but the combobox B's UI do not be changed.)
All the other structure is same between A and B. What is the reason?
MainWindow.xaml
...
<ComboBox ItemsSource="{Binding FruitList}" SelectedItem="{Binding SelectedFruit}"
DisplayMemberPath="FruitName" />
<Button Content="Button" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" DisplayMemberPath="ItemName" />
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click2"/>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
FruitList.Add(f1);
FruitList.Add(f2);
FruitList.Add(f3);
UserControlList.Add(u1);
UserControlList.Add(u2);
UserControlList.Add(u3);
}
Fruit f1 = new Fruit { FruitName = "Apple" };
Fruit f2 = new Fruit { FruitName = "Banana" };
Fruit f3 = new Fruit { FruitName = "Lemon" };
MyUserControl u1 = new MyUserControl { ItemName = "Apple" };
MyUserControl u2 = new MyUserControl { ItemName = "Banana" };
MyUserControl u3 = new MyUserControl { ItemName = "Lemon" };
ObservableCollection<Fruit> _FruitList = new ObservableCollection<Fruit>();
public ObservableCollection<Fruit> FruitList
{
get { return _FruitList; }
set
{
_FruitList = value;
OnPropertyChanged();
}
}
Fruit _SelectedFruit;
public Fruit SelectedFruit
{
get { return _SelectedFruit; }
set
{
_SelectedFruit = value;
OnPropertyChanged();
}
}
ObservableCollection<MyUserControl> _UserControlList = new ObservableCollection<MyUserControl>();
public ObservableCollection<MyUserControl> UserControlList
{
get
{
return _UserControlList;
}
set
{
_UserControlList = value;
OnPropertyChanged();
}
}
MyUserControl _SelectedUserControl;
public MyUserControl SelectedUserControl
{
get { return _SelectedUserControl; }
set
{
_SelectedUserControl = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedFruit = f3;
}
private void Button_Click2(object sender, RoutedEventArgs e)
{
this.SelectedUserControl = u3;
}
}
public class Fruit
{
public string FruitName { get; set; }
}
}
UserControl
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string ItemName { get; set; }
}
This is not the good way of achieving this. Better define the ItemTemplate for the combobox to have the UserControl in it like:
<ComboBox ItemsSource="{Binding ItemList}" SelectedItem="{Binding SelectedItem}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<myControls:MyUserControl/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and define the class Item
public class Item
{
public string ItemName { get; set; }
}
ObservableCollection<Item> _ItemsList = new ObservableCollection<Item>();
public ObservableCollection<Item> ItemsList
{
get
{
return _ItemsList ;
}
set
{
_ItemsList = value;
OnPropertyChanged();
}
}
Here DataContext of your UserControl will be Item object. you can bind the ItemName within you user control to show it in anyway you want.
in your user control you can have:
<TextBlock Text="{Binding ItemName}"></TextBlock>
Since you have asked "What is the reason?":
The reason why the second combo box does not show any selection is that ComboBox handles items of type ContentControl specially. In the read-only selection box, it is not the ContentControl that is used to display the value, but the content of the ContentControl. Since a UserControl is a ContentControl, the content of the UserControl is displayed inside the selection box, and therefore you have lost the data context of the UserControl; in the end, an empty string is displayed even though SelectedItem contains a reference to the UserControl that still has a valid data context. (As far as I know this behavior is undocumented; but you can see that it works like this by examining the ComboBox's code on http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ComboBox.cs, especially the UpdateSelectionBoxItem() method).
By setting IsEditable="True" on the second ComboBox, you can see that everything works fine if the combo box has no read-only selection box.
Therefore, you generally should avoid adding UI elements to combo boxes, especially if you are using the DisplayMemberPath property, i.e. if you never want to actually display the UI element.
The recommended way to display ComboBox items with non-standard appearance (e.g. with UserControls) is described in the answer of #nit.
If you, however, insist on passing a UserControl item list to the ComboBox, you might remove DisplayMemberPath and use something like this:
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Furthermore, in the constructor of your UserControl, you must place this line:
((FrameworkElement) Content).DataContext = this;
This is necessary to make sure that the correct data context is available in the read-only selection box, which only contains the content of the user control, not the user control itself.
Please note, that with the above example, the drop-down list contains text only (i.e. the item names), but the selection box will contain the fully rendered user control.

WPF Listbox SelectionChanged

I currently have an Entity that has a collection property on it. I want to know if why would the SelectionChanged only fire once and it won't trigger the SelectionChanged again once I try to select the item that was previously selected.
MainWindowViewModel
public MainWindowViewModel()
{
var a = new List<Test>();
a.Add(new Test() { Name = "Leo", Test1 = new List<Test1> { new Test1() { Content = "aaa"} } });
a.Add(new Test() { Name = "2", Test1 = new List<Test1> { new Test1() { Content = "bbb"} } });
a.Add(new Test() { Name = "Le33o", Test1 = new List<Test1> { new Test1() { Content = "ccc"} } });
A = a;
}
private List<Test> _a;
public List<Test> A
{
get { return _a; }
set { _a = value; OnPropertyChanged("A");}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
My Mainwindow
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
private void Test(object sender, SelectionChangedEventArgs e)
{
}
My listbox structure
public class Test
{
public List<Test1> Test1 { get; set; }
public string Name
{
get;set;
}
}
public class Test1
{
public string Content { get; set; }
}
I select the first object, the event fires, I select the second object, the event fires, I select the first object, the event doesn't fire, I select third object, the event fires. It seems like it only triggers and calls the event once.
My XAML Code:
<ItemsControl x:Name="Lists" ItemsSource="{Binding A}" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Style="{StaticResource DefaultTextBlockStyle}" />
<ListBox SelectionChanged="Test" ItemsSource="{Binding Test1}"
Margin="5,0,0,0" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The test method is just an empty method I just want to hit the breakpoint every time I change.
private void Test(object sender, SelectionChangedEventArgs e)
{
}
Update 1: I tried to reproduce this in a simple WPF app, it seems that the ListBoxItem is getting IsEnabled to false but I snooped it and all the controls are enabled. It just getting a grey background that looks like disabled. Will try to investigate further.
Update 2: It seems that the ListBoxItem IsSelected property is not being unset when you change an item.
To answer your question ...
I want to know if why would the SelectionChanged only fire once and it won't trigger the SelectionChanged again once I try to select the
item that was previously selected.
... in a learning by doing way
open a new WPF Project add 2 Listboxes create ONE SelectionChanged event for both and add some items to each Listbox
let's mention it look's now like this
<ListBox Height="100" Name="listBox1" Width="120" SelectionChanged="listBox_SelectionChanged"/>
<ListBox Height="100" Name="listBox2" Width="120" SelectionChanged="listBox_SelectionChanged"/>
.
var list = new List<string>();
list.Add("Element1");
list.Add("Element2");
list.Add("Element3");
list.Add("Element4");
listBox1.ItemsSource = list;
listBox2.ItemsSource = list;
If you now select Element1 in listBox1 your listBox_SelectionChanged get triggert after that select Element2 in your listBox2 so your listBox_SelectionChanged get's triggert again.
If you take a closer look at your listBox1 you will see that the Background behind your Element1 is gray which means it is selected, but with out focuse. If you select now the Element1 in your listBox1 again the listBox_SelectionChanged doesn't get triggert because the selection does't change only the Focuse does.
That's the exact same "problem" is in your code because your DataTemplate does the same think we did as we added our 2 Listboxes just automatically
as simple and dirt workaround you could use the following code
private object seletedItem;
private ListBox ItemsHost;
private void Test(object sender, SelectionChangedEventArgs e)
{
var buff = sender as ListBox;
if (seletedItem != null)
if (ItemsHost != buff)
ItemsHost.SelectedItem = null;
ItemsHost = buff;
if (e.AddedItems.Count > 0)
seletedItem = e.AddedItems[0];
}
The simple solution I found out is to make selectedItem as null in the event handler.
private void tempList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Card selectedOffer = (TempList.SelectedItem as Card);
if (selectedOffer != null)
{
MessageBox.Show(selectedOffer._id);
}
ListBoxNeeded.SelectedItem = null;
}
Selecting the same item is not a SelectionChanged event. The selection did not change.
The problem statement is not clear.
Break it down. This works for me. If I select any item a second time, 3rd, 4th time the event fires.
OP asserted it does not work if it is a List in a List. Still works for me.
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
public List<ListList> ListList1
{
get { return new List<ListList>{new ListList("name1", new List<string> { "one", "two", "three" })}; }
}
private void Test(object sender, SelectionChangedEventArgs e)
{
ListBox lb = (ListBox)sender;
System.Diagnostics.Debug.WriteLine(lb.SelectedItem.ToString());
}
public class ListList
{
public string Name { get; set; }
public List<string> Values { get; set; }
public ListList(string name, List<string> values) { Name = name; Values = values; }
}
<ListBox ItemsSource="{Binding Path=ListList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox SelectionChanged="Test" ItemsSource="{Binding Path=Values}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources