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}"
Related
I'm binding a ListView to an ICollectionView in my viewmodel. The ICollectionView has some predefined filters that are applied when you click some buttons. However I cannot seem to find any way to (auto) select the first item in the ListView after the collection has been filtered.
I've tried to set SelectedIndex=0, add both Target and Source notification to the binding, but all are ineffective when the filter applies.
Any pointers on how to achieve this?
EDIT: Below code illustrates my issue I'd say.
XAML:
<Window x:Class="CollectionViewTest.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:local="clr-namespace:CollectionViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- MENU -->
<StackPanel Orientation="Vertical">
<Button Content="Numbers below 4" Click="Below4_Click" Width="100"/>
<Button Content="Numbers below 7" Click="Below7_Click" Width="100"/>
<Button Content="All numbers" Click="All_Click" Width="100"/>
</StackPanel>
<!-- LIST -->
<ListView
Grid.Column="1"
SelectedIndex="0"
ItemsSource="{Binding Numbers, Mode=OneWay}"
SelectedItem="{Binding SelectedNumber, Mode=TwoWay}">
<ListView.Resources>
<DataTemplate DataType="{x:Type local:Number}">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ListView.Resources>
</ListView>
<!-- DETAILS -->
<TextBlock Grid.Column="2" Text="{Binding SelectedNumber.Text}" Width="100"/>
</Grid>
</Window>
Code-Behind:
using System.Windows;
namespace CollectionViewTest
{
public partial class MainWindow : Window
{
private MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = (MainViewModel)DataContext;
}
private void Below4_Click(object sender, RoutedEventArgs e)
{
vm.MenuFilter = f => f.Value < 4;
}
private void Below7_Click(object sender, RoutedEventArgs e)
{
vm.MenuFilter = f => f.Value < 7;
}
private void All_Click(object sender, RoutedEventArgs e)
{
vm.MenuFilter = f => true;
}
}
}
ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Data;
using System.Collections.ObjectModel;
namespace CollectionViewTest
{
public class MainViewModel : PropertyChangedBase
{
public MainViewModel()
{
Numbers = new ObservableCollection<Number>();
NumberCollection = CollectionViewSource.GetDefaultView(Numbers);
NumberCollection.Filter = Filter;
NumberCollection.SortDescriptions.Add(new SortDescription("Value", ListSortDirection.Ascending));
for (int i = 0; i < 10; i++)
Numbers.Add(new Number { Value = i, Text = $"This is number {i}." });
}
private Func<Number, bool> menuFilter;
public Func<Number, bool> MenuFilter
{
get => menuFilter;
set
{
menuFilter = value;
NumberCollection.Refresh();
}
}
private bool Filter(object item)
{
var number = (Number)item;
return MenuFilter == null ? true : MenuFilter(number);
}
public ObservableCollection<Number> Numbers { get; set; }
public ICollectionView NumberCollection { get; set; }
private Number selectedNumber;
public Number SelectedNumber { get => selectedNumber; set => Set(ref selectedNumber, value); }
}
public class Number : PropertyChangedBase
{
public int Value { get; set; }
private string text;
public string Text { get => text; set => Set(ref text, value); }
}
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
As you can see, pressing one of the buttons changes the Filter and calls Refresh on the collection. What I would like to have, is that the first item in the list (here '0') is selected automatically which then would display the text "This is number 0" in the text in column 2.
I have tried both the SelectedIndex=0 and also MoveCurrentToFirst but nothing is selected.
Don't set SelectedIndex when binding to an ICollectionView. Instead, set its CurrentItem via MoveCurrentTo() or MoveCurrentToFirst():
myCollectionView.MoveCurrentTo(someItem);
...
myCollectionView.MoveCurrentToFirst();
Also, set IsSynchronizedWithCurrentItem on your ListView:
<ListView IsSynchronizedWithCurrentItem="True" ...
Detect when filter is applied
When the filter is evaluated, the collection view is refreshed which in turn resets the collection. To detect this, listen for the CollectionChanged event and look for the NotifyCollectionChangedAction.Reset flag. Please refer to the CollectionView source code for more details.
I have a simple ComboBox which looks like following:
<ComboBox ItemsSource="{DynamicResource ItemsCompColl}"
TextSearch.TextPath="ItemName"
SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
Grid.IsSharedSizeScope="True">
........................
</ComboBox>
It works well. Now, I am using the ItemId property which is bound in SelectedValue to check if user has selected an appropriate item from comboBox or not.
Issues:
When user selects a value from ComboBox, the ItemId property is set to the Id of the Selected Item in ComboBox. After that if user goes to next Control and returns to ComboBox and enters some garbage value to ComboBox, the ItemId of ComboBox does not change, I mean it's not reset to "0". So, my validation fails and user succeeds in entering the garbage values.
OK, so you want to Set SelectedValue to 0 when there is any validation error in the editable TextBox of theComboBox. You need to check the validation result of the Text and then reset your SelectedValue to 0 if the validation fails.
Here is an working example for you:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ComboBox ItemsSource="{Binding ComboboxItems}"
IsEditable="True" DisplayMemberPath="ItemName"
Text="{Binding SelectedName, ValidatesOnDataErrors=True}"
SelectedValue="{Binding SelectedID, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="ItemId"
Grid.IsSharedSizeScope="True">
</ComboBox>
<TextBox Text="{Binding SelectedID,UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
</Window>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
MyViewModel mvm;
public MainWindow()
{
InitializeComponent();
mvm = new MyViewModel()
{
ComboboxItems = new ObservableCollection<ComboItem>()
{
new ComboItem{ItemName="item1",ItemId=1},
new ComboItem{ItemName="item2",ItemId=2},
new ComboItem{ItemName="item3",ItemId=3}
},
};
this.DataContext = mvm;
}
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void RaisePropertyChanged(String propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
public class ComboItem : ObservableObject
{
private string _itemname;
private int _itemid;
public string ItemName
{
get
{
return _itemname;
}
set
{
_itemname = value;
RaisePropertyChanged("ItemName");
}
}
public int ItemId
{
get { return _itemid; }
set
{
_itemid = value;
RaisePropertyChanged("ItemId");
}
}
}
public class MyViewModel : ObservableObject, IDataErrorInfo
{
private int _selectedid;
private string _selectedname;
public ObservableCollection<ComboItem> ComboboxItems
{
get;
set;
}
public int SelectedID
{
get { return _selectedid; }
set
{
if (_selectedid != value)
{
_selectedid = value;
RaisePropertyChanged("SelectedID");
}
}
}
public string SelectedName
{
get { return _selectedname; }
set
{
if (_selectedname != value)
{
_selectedname = value;
RaisePropertyChanged("SelectedName");
}
}
}
public string Error
{
get { return this[SelectedName]; }
}
public string this[string columnName]
{
get {
switch (columnName)
{
case "SelectedName":
{
if (SelectedName!=null && ComboboxItems.Count(x => x.ItemName == SelectedName) == 0)
{
//reset selected value to 0
this.SelectedID = 0;
return "Invalid selection";
}
break;
}
}
return null;
}
}
}
}
Result:
When user enter valid text (e.g. item1), the Textbox below shows the correct ItemId of the SelectedValue, and when user enter invalid text, the selected value will be reset to 0.
P.S: When garbage is entered in ComboBox, it will always display validation error indicator (red border as shown above), and if you data-bind SelectedItem to a property, it will be null. So you shouldn't care about the SelectedValue if there is an error, that's why I was saying I can't reproduce the error in the comments.
You really got me with this, i've never realized this problem existed. I've found a solution that works it you don't care to clear the combo on focus. There are probably better ways, but none i can think about. Maybe someone out there has another solution.
First of all, add a reference to Windows.System.Interactivity in your proyect, and add this to your XAML:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Then, add this code to the combobox:
<ComboBox ItemsSource="{DynamicResource ItemsCompColl}"
TextSearch.TextPath="ItemName" x:Name="cbItems"
SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
Grid.IsSharedSizeScope="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotMouseCapture">
<i:InvokeCommandAction Command="{Binding ClearCombo}"
CommandParameter="{Binding ElementName=cbItems}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Finally, let's create the command in ouw View Model:
RelayCommand<System.Windows.Controls.ComboBox> _clearCombo;
public ICommand ClearCombo
{
get
{
if (_clearCombo == null)
{
_clearCombo = new RelayCommand<System.Windows.Controls.ComboBox>(this.ClearComboCommandExecuted,
param => this.ClearComboCommandCanExecute());
}
return _clearCombo;
}
}
private bool ClearComboCommandCanExecute()
{
return true;
}
private void ClearComboCommandExecuted(System.Windows.Controls.ComboBox cb)
{
cb.Text = "";
}
Hope this helps with your problem.
Edit
Ok, after #XAMlMAX comment, I think he is right and this may be done in Code Behind easily and probably it's better in MVVM pattern. Simply add a event handler to the combobox to capture GotMouseCapture:
<ComboBox ItemsSource="{DynamicResource ItemsCompColl}"
TextSearch.TextPath="ItemName" x:Name="cbItems"
SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
Grid.IsSharedSizeScope="True"
GotMouseCapture="cbItems_GotMouseCapture" >
And then in code behind of the View:
private void cbItems_GotMouseCapture(object sender, MouseEventArgs e)
{
((ComboBox)sender).Text = "";
}
Edit 2
Well, one final, ugly idea to solve it. I don't like it at all, but maybe it solves your problem.
First of all, you must subscribe to the TextBoxBase.TextChanged event:
<ComboBox ItemsSource="{DynamicResource ItemsCompColl}"
TextSearch.TextPath="ItemName" x:Name="cbItems"
SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
Grid.IsSharedSizeScope="True"
TextBoxBase.TextChanged="cbItems_TextChanged" >
Then in code behind add this code:
private void cbItems_TextChanged(object sender, TextChangedEventArgs e)
{
string text = ((ComboBox)sender).Text;
((YourViewModel)this.DataContext).ItemId= text;
}
This way, you make sure any time the ComboBox changes its text, you get notified about it. It's really horrible code, but i've runned out of ideas...
I am trying to automatically select an item in a ListView whenever one is added to an ObservableCollection. I am using the CollectionChanged event to listen for when an item is added and then select it. The CollectionChanged event seems to occur before the UI is updated and so the SelectedIndex is adjusted accordingly. I have tried setting both the SelectedIndex and the SelectedItem but in both cases the item after the one added ends up being selected. The correct index is the when the collection is changed, the UI updates and then the the index is incremented by the ListView.
The phenomenon can be demonstrated with the following:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding Main, Source= {StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Items, Mode=TwoWay}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Grid.Row="0">
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Content="Add Item" Width="75" Command="{Binding AddItemCommand}"/>
<Label Content="SelectedIndex:"/>
<Label Content="{Binding SelectedIndex}"/>
<Label Content="SelectedItem:"/>
<Label Content="{Binding SelectedItem}"/>
<Label Content="<- Numbers should match after item added"/>
</StackPanel>
</Grid>
</Window>
and ViewModel:
public class MainViewModel : ViewModelBase
{
private ICommand addItemCommand;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
Items = new ObservableCollection<string>();
Items.CollectionChanged += Items_CollectionChanged;
}
private int selectedIndex = -1;
public const string SelectedIndexPropertyName = "SelectedIndex";
public int SelectedIndex
{
get
{
return selectedIndex;
}
set
{
if (selectedIndex != value)
{
selectedIndex = value;
RaisePropertyChanged(SelectedIndexPropertyName);
}
}
}
private string selectedItem = null;
public const string SelectedItemPropertyName = "SelectedItem";
public string SelectedItem
{
get
{
return selectedItem;
}
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged(SelectedItemPropertyName);
}
}
}
private ObservableCollection<string> items;
public const string ItemsPropertyName = "Items";
public ObservableCollection<string> Items
{
get
{
return items;
}
set
{
items = value;
RaisePropertyChanged(ItemsPropertyName);
}
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
//SelectedItem = e.NewItems[0];
SelectedIndex = e.NewStartingIndex;
}
}
public ICommand AddItemCommand
{
get
{
if (addItemCommand == null)
addItemCommand = new RelayCommand(() => Items.Add("Item " + items.Count));
return addItemCommand;
}
}
}
I have added uploaded a example solution to www.itzalive.co.uk/AddItemSelection.zip
Anybody know how to end up with the newly added item selected using the CollectionChanged event? In my actual program the item is not added in the same place it is being displayed so it is not possible to set the selected item separately.
It is better to use just the SelectedItem property, rather than the SelectedIndex property as well:
<ListView ItemsSource="{Binding Items, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" ... />
Then in code:
YourClass newItem = new YourClass();
Items.Add(newItem);
SelectedItem = newItem;
Also remove your code from the Items_CollectionChanged event handler. You may also need to call NotifyPropertyChanged("Items"); to get the UI to update if it doesn't already.
UPDATE >>>
Try adding this into your CollectionChanged handler then:
if (e.Action == NotifyCollectionChangedAction.Add)
SelectedItem = e.NewItems[0] as string;
I need to create an UI which allows me to select entries from one list box and add it to another listbox at the run time. Now, the listbox1 may contain combo box and checkbox as the items.
For example, if I add a combo box labelled Quarter with values "Q1, Q2, Q3, Q4" as an item in listbox1 and select the entry Q1 in it, and click on the "Add" button, it should be added to listbox2. Vice versa should also be possible. This should be possible at the run time. How could I add combo box and checkbox as an item to the listbox? Also, please suggest if for the add-remove buttons, the code I've is correct.
private void MoveListBoxItems(ListBox source, ListBox destination)
{
ListBox.SelectedObjectCollection sourceItems = source.SelectedItems;
foreach (var item in sourceItems)
{
destination.Items.Add(item);
}
while (source.SelectedItems.Count > 0)
{
source.Items.Remove(source.SelectedItems[0]);
}
}
private void button1_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox1, listBox2);
}
private void button2_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox2, listBox1);
}
This is a WPF solution to your need. I am posting it because you told me it could be useful for you. It largely surpasses anything you can ever hope to achieve in winforms, which is a very limited and outdated technology.
This is how it looks in my screen:
I am using some simple ViewModels to represent the data:
ListItemViewModel (the "base" one):
public class ListItemViewModel: ViewModelBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
NotifyPropertyChange(() => DisplayName);
}
}
}
BoolListItemViewModel (for CheckBoxes):
public class BoolListItemViewModel: ListItemViewModel
{
private bool _value;
public bool Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged(() => Value);
}
}
}
SelectableListItemViewModel (for ComboBoxes):
public class SelectableListItemViewModel: ListItemViewModel
{
private ObservableCollection<ListItemViewModel> _itemsSource;
public ObservableCollection<ListItemViewModel> ItemsSource
{
get { return _itemsSource ?? (_itemsSource = new ObservableCollection<ListItemViewModel>()); }
}
private ListItemViewModel _selectedItem;
public ListItemViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChange(() => SelectedItem);
}
}
}
This is the "Main" ViewModel, which holds the 2 lists and the Commands (the Button actions)
public class ListBoxSampleViewModel: ViewModelBase
{
private ObservableCollection<ListItemViewModel> _leftItems;
public ObservableCollection<ListItemViewModel> LeftItems
{
get { return _leftItems ?? (_leftItems = new ObservableCollection<ListItemViewModel>()); }
}
private ObservableCollection<ListItemViewModel> _rightItems;
public ObservableCollection<ListItemViewModel> RightItems
{
get { return _rightItems ?? (_rightItems = new ObservableCollection<ListItemViewModel>()); }
}
private DelegateCommand<ListItemViewModel> _moveToRightCommand;
public DelegateCommand<ListItemViewModel> MoveToRightCommand
{
get { return _moveToRightCommand ?? (_moveToRightCommand = new DelegateCommand<ListItemViewModel>(MoveToRight)); }
}
private void MoveToRight(ListItemViewModel item)
{
if (item != null)
{
LeftItems.Remove(item);
RightItems.Add(item);
}
}
private DelegateCommand<ListItemViewModel> _moveToLeftCommand;
public DelegateCommand<ListItemViewModel> MoveToLeftCommand
{
get { return _moveToLeftCommand ?? (_moveToLeftCommand = new DelegateCommand<ListItemViewModel>(MoveToLeft)); }
}
private void MoveToLeft(ListItemViewModel item)
{
if (item != null)
{
RightItems.Remove(item);
LeftItems.Add(item);
}
}
}
This is the entire XAML for the Window:
<Window x:Class="WpfApplication4.Window14"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window14" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListItemViewModel}">
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BoolListItemViewModel}">
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding Value}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectableListItemViewModel}">
<ComboBox ItemsSource="{Binding ItemsSource}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Stretch" MinWidth="100"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding LeftItems}"
x:Name="LeftList"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<Button Content="Move to Right"
Command="{Binding MoveToRightCommand}"
CommandParameter="{Binding SelectedItem,ElementName=LeftList}"/>
<Button Content="Move to Left"
Command="{Binding MoveToLeftCommand}"
CommandParameter="{Binding SelectedItem,ElementName=RightList}"/>
</StackPanel>
<ListBox ItemsSource="{Binding RightItems}"
Grid.Column="2" x:Name="RightList"/>
</Grid>
</Window>
and finally, this is the Window Code-behind, which only initializes the ViewModel with some items:
public partial class Window14 : Window
{
public Window14()
{
InitializeComponent();
DataContext = new ListBoxSampleViewModel()
{
LeftItems =
{
new ListItemViewModel(){DisplayName = "Item1"},
new BoolListItemViewModel() {DisplayName = "Check Item 2", Value = true},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Combo Item 1"},
new BoolListItemViewModel() {DisplayName = "Check inside Combo"},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Wow, this is awesome"},
new BoolListItemViewModel() {DisplayName = "Another CheckBox"}
}
}
}
}
}
};
}
}
At first glance, this might seem like a LOT of code... but if you take 2 seconds to analyze it... Its just "simple, simple properties and INotifyPropertyChanged. That's how you program in WPF.
I'm talking about a completely different paradigm from what you might be used to in winforms, but it's really worth the effort of learning it. Notice that nowhere in my code I am interacting with UI elements. I just create the ViewModel structure and let the WPF Binding System to take care of generating the UI for me, using the provided DataTemplates.
I'm using the ViewModelBase from MVVM Light and the DelegateCommand from WPFTutorial.net. You can copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself (you will also need these 2 classes from the links above)
If you need to integrate this in an existing winforms application, you will need the ElementHost
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>