WPF Listbox SelectionChanged - c#

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>

Related

ListView: Edit and save SelectedItem with a Button only

I have a ListView that is bound on an ObservableCollection.
<ListView Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Margin="5" Name="CustomerListView" ItemsSource="{Binding Customers}" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,0,0" Text="{Binding LastName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the same View i have some TextBoxes which are meant to edit the CurrentCustomer. I also have a save button. If you click this button the modifications of the CurrentCustomer should be saved. If the button "cancel" is pressed the modifications should be discarded.
<TextBox Name="CustomerSalutationTextBox" Grid.Column="1" Grid.Row="0" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.Salutation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Problem is, if i make some changes on the currentCusomer, they are taking effect immediately.
Do you have a solution?
What you need to add in your ViewModel / the class you have a binding context to is to save what was previous in the Textfield.
And when you hit abort, u just overwrite your newValue with the old one.
I'm going to setup a small example.
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
private string _initialCustomerName;
private string _initialCustomerLastName;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
this._initialCustomerName = customerName;
this._initialCustomerLastName = customerLastName;
}
//example event handler for your abort button
private void OnAbortButtonClick(object sender, EventArgs args) {
this.CustomerName = this._initialCustomerName; //set the initial name
this.CustomerLastName = this._initialCustomerLastName; //set the initial lastName
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternative
As you might load your data from a database/csv file/something else, you should know the original values. When pressing the cancel button, you could invoke a CancelButtonClicked event in your ViewModel and some other class which subscribed to the ViewModels event and knows the original Model could set the original values on that viewModel instance, or just exchange the ViewModel instance with the original one.
Have a look at : https://msdn.microsoft.com/en-us/library/hh848246.aspx
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
public event CancelButtonClicked CancelButtonClicked;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
}
private void OnAbortButtonClick(object sender, EventArgs args) {
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
internal delegate void CancelButtonClicked(object sender);
public class SomeOtherClass {
private ExampleViewModel _viewModel;
public SomeOtherClass() {
this._viewModel = new ExampleViewModel("foo", "bar");
this._viewModel.CancelButtonClicked += ViewModelOnCancelButtonClicked;
}
private void ViewModelOnCancelButtonClicked(object sender) {
ExampleViewModel vm = sender as ExampleViewModel;
vm.CustomerName = "foo"; //set the initial values again
vm.CustomerLastName = "bar";
}
}
Alternative2
You could also exchange the complete VM when the event of the cancel button is invoked to retreive its original state.
Alternative3
Everytime your SelectedItem changes, you could save the current state of it by creating a copy of it. When your CancelButton is pressed, you set the SelectedItem to the copy of your original viewModel.
You'd need a copy constructor or a copy method for that purpose.
I've found out another solution. In the code behind of the view i've added following:
void saveButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = customerFirstNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
My textbox with UpdateSourceTrigger Explicit
<TextBox Name="customerFirstNameTextBox" Grid.Column="1" Grid.Row="2" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" IsEnabled="{Binding Path=IsCustomerTextEnabled}"/>
And my button
<Button Name="SaveButton" Click="saveButton_Click" Margin="5" Content="Save"/>

Update a ViewModel and pass filter when adding item to ObservableCollection<string>

I'm new with the ICollectionView and I'm currently trying to filter a list of object.
Here is my ViewModel :
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<RevitFamily> _myData;
public ObservableCollection<RevitFamily> MyData
{
get { return _myData; }
}
string searchName = string.Empty;
ObservableCollection<string> searchKeywords = new ObservableCollection<string>();
public string SearchName
{
get { return searchName; }
set
{
searchName = value;
myDataView.Filter = FilterName;
OnPropertyChanged("SearchName");
}
}
public ObservableCollection<string> SearchKeywords
{
get { return searchKeywords; }
set
{
searchKeywords = value;
myDataView.Filter = FilterName;
OnPropertyChanged("SearchKeywords");
}
}
ICollectionView myDataView;
public ViewModel()
{
_myData = new ObservableCollection<RevitFamily>();
myDataView = CollectionViewSource.GetDefaultView(_myData);
//when the current selected changes store it in the CurrentSelectedPerson
myDataView.CurrentChanged += delegate
{
//stores the current selected person
CurrentSelectedFamily = (RevitFamily)myDataView.CurrentItem;
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I add an item in the ObservableCollection "SearchKeywords", the list is correctly updated but the notification "OnPropertyChanged" is not call. How can I do that ?
EDIT : I added the XAML part and the Add methode.
Here is the XAML code that bind the ObservableCollection.
<Border Grid.Row="6" Grid.ColumnSpan="3" Height="100">
<ItemsControl x:Name="ListKeywords" ItemsSource="{Binding SearchKeywords, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CrossLabel MyLabel="{Binding}" Remove="Kw_Remove"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
And here is the Methode
private void Kw_Add(object sender, RoutedEventArgs e)
{
if (!_families.SearchKeywords.Contains(this.Keywords.Text))
{
_families.SearchKeywords.Add(this.Keywords.Text);
}
}
When I add the keyword to "_families.SearchKeywords" the ItemControle get the new item but the filter whish is with the ViewModel do not apply.
Just subscribe to the CollectionChanged event in your constructor, no need to replace the collection each time.
public ViewModel()
{
searchKeywords.CollectionChanged += searchKeywords_CollectionChanged;
}
void searchKeywords_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
throw new NotImplementedException();
}
Adding an item to an ObservableCollection causes the collection to fire its CollectionChanged event. This unrelated to OnPropertyChanged. Your SearchKeywords property is a property of your ViewModel class - your OnPropertyChanged method is only going to be called if you actually change the value of SearchKeywords, i.e. replace the ObservableCollection with an entirely different ObservableCollection.

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}"

Extended selection mode, virtualization and IsSelected binding

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();
}

How could I add combox and other items in a listbox?

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

Categories

Resources