how to add\remove multiple items from one listview to another in MVVM using wpf? - wpf

I have two listviews. One of left handside and another on right hand side. I have two buttons to add and remove items from the two listviews.
LHSListview is bound to List and RHSListview is bound to List. Column class has two variables 'order' and 'Id'.
when I click on the add button all the selected items from LHSListview must move to RHSListview. And vice versa when clicked on remove button.
This is what I am trying to do on the click of add button
var list1 = new ArrayList(lstAllFields.SelectedItems);
foreach (var item in list1)
{
lstAllFields.Items.Remove(item);
SelectedFields.Items.Add(item);
}
But this throws an error on lstAllFields.Items.Remove(item); this line saying "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead."

You mentioned you're using MVVM, so you probably know that you shouldn't be changing the items from the ListViews inside the view. What you should do is modify the collection you are bound to in the ViewModel.
Problem is it's kind of tricky to get the multiple selections in MVVM, because the SelectedItems property isn't a Dependency Property.
There are 2 ways to achieve what you're after, both support MVVM:
The shorter and easier way is to listen to the Button_Click in the View's CodeBehind, create a new list of the selected items and pass it to the VM to do the logic of adding and removing items.
So a short version would look like this:
Code Behind:
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
List<MyObject> mySelectedItems = new List<MyObject>();
foreach (MyObject item in listview1.SelectedItems)
{
mySelectedItems.Add(item);
}
(this.DataContext as MainVM).MoveMethod(mySelectedItems);
}
View Model (in my class I called it MainVM)
public void MoveMethod(List<MyObject> selected)
{
foreach (var item in selected)
{
List1.Remove(item);
List2.Add(item);
}
}
That's it. Just remember, the List1 and List2 (which are the ItemSource's that ListView1 and ListView2 bind to, must be ObservableCollection to see the update in the UI.
I promised a longer option too, for that see the great 3-part blog post on the subject:
MVVM and Multiple Selection – Part I
MVVM and Multiple Selection – Part II
MVVM and Multiple Selection – Part III

Related

How do I get the UIElement when Collection changes?

I have a wpf Treeview which has a dynamic itemssource. The User can add and remove items at runtime.
I'm missing an event which gives me the currently added UIElement that was added to the treeviews itemsSource. So I guess I need to switch to OnCollectionChanged.
This is what I have:
// MyItemViewModel is a viewmodel for a TreeViewItem
// MyCollection is bound to hte Treeview's ItemsSource
public class MyCollection : ObservableCollection<MyItemViewModel>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
// i like to have the UIelement which was added to the collection
// (e.NewItems contains the FrameworkElement's DataContext)
break;
}
}
}
Im following MVVM, as good as I can, and don't want to hold any view elements in the viewmodel.
I like to have an event that is fired when an item is added, which provides the new added UIElement in its sender or EventArgs.
I already tried ItemContainerGenerator class, but it's not useful inside a viewmodel since it requires already a UIElement Control.
You seem to be looking at this problem from the wrong direction... in MVVM, you can pretty much forget about the UI for the most part. So, instead of thinking how to get hold of the item that the user added into the collection control in the UI, think about accessing the data object that you added to the data collection in the view model that is data bound to the UI collection control in response to an ICommand that was initiated by the user.
So to me, it sounds like you need to implement an ICommand that is connected to a Button in the UI, where you add the new item into the data bound collection rather than any event. In this way, you'll always know the state of all of your data items.

TabControl - databinding TabItem order

I've got a datababound TabControl and would like to bind the index of each TabItem to a corresponding property in my view model. The ItemsSource is an ObservableCollection, and I'm using Bea Stollnitz's Drag/Drop functionality to provide tab control re-ordering.
My gut feeling is that it should be able to be handled in the data template for the tab item header, but I haven't been able to get it working.
Your TabControl.ItemsSource should be bound to your collection, so to re-arrange the order of tab items, simply re-arrange the collection.
I've worked with Bea's drag/drop code before to create a TabControl that allowed users to drag/drop the tab items, and I think most of what was needed is in the code she provides. On drop, it removes the dragged object from it's parent collection, and inserts it to its new location in the drop target collection, which in your case is the same collection.
Edit
Based on your comment below about updating your ViewModel with the Tab Index, try using the CollectionChanged event.
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
foreach (var item in MyCollection)
item.TabIndex = MyCollection.IndexOf(item);
}

Auto cancel changes in DataForm when selection is changed

I wonder how you do such thing. Assume, we have MVVM CRUD app which modifies a tree (menu structure, for example). We have a view model with the menu items and two views: the first with a TreeView and the second with a DataForm. Main problems are:
DataForm can not handle
hierarchical data.
Depending on the menu item selected
in the TreeView the DataForm
should display different set of
fields (for example for menu items
with children or without).
I've ended up with the following. View model has 3 fields:
Items — the collection of
MenuItem objects which have their
own Children collection for
building hierarchical data source.
SelectedItem — currently selected
MenuItem in the TreeView.
EditedItem — EditViewModel
object which basically has two
descendants: MenuItemEditViewModel
and LeafMenuItemEditViewModel.
This property is set automatically
when SelectedItem is changed. Its
actual type is inferred from the
SelectedItem.Children emptiness.
TreeView is bound to Items and SelectedItem. DataForm is not required to maintain currency in this case (instead current item is set by the TreeView) nor it is responsible for creating and deleting items. That's why I decided to bind only its CurrentItem to view model's EditedItem (ItemsSource is unbound). Its AutoCommit is set to False (when it is True and ItemsSource is unbound all current item changes get copied to newly selected item when you select different item in the TreeView, which is not so nice). DataForm fields are autogenerated.
Obviously, that now if we select an item in the TreeView, then make some changes in the DataForm and try to select different item in the TreeView we'll get well-known
Cannot change currency when an item
has validation errors or it is being
edited and AutoCommit is false. Set
ItemsSource to a ICollectionView to
manage currency instead
In this case I want DataForm to discard all changes implicitly. There is a workaround to call DataForm.CancelEdit() before TreeView selected item is changed (usually an event like PreviewSelectionChanged or BeforeSelectionChanged). But it is not the MVVM way since the TreeView and the DataForm are defined in completely different views (read: is not acceptable).
Is there something like AutoCancel which forces DataForm to cancel changes when its CurrentItem is changed? Maybe someone from dev team can answer? Or how would you deal with such problem?
I was surprised to find the Silverlight is severly lacking in this functionality, considering all the business oriented RIA functionality. AutoCommit is not acceptable to me because I want the user to explicitly acknowledge pending changes, rather than just commit something to the database that they may not want.
You can reliably track the edit mode of the DataForm using a private member variable and trapping the BeginningEdit and EditEnded events of the DataForm (naming inconsistency! Why one is called xxxEdit and the others are Editxxx is beyond me. Should it not be EditBeginning and EditEnded??). Inside the event handler for BeginningEdit, set the flag to true and set it to false in EditEnded.
In your SelectionChanged event, you can then check the flag. If it is true, you can call the CancelEdit on the DataForm.
private bool _editing = false;
public MainPage() {
DataForm1.BeinningEdit +=
new EventHandler<CancelEventArgs>(DataForm1_BeginningEdit);
DataForm1.EditEnded +=
new EventHandler<DataFormEditEndedEventArgs>(DataForm1_EditEnded);
}
protected void DataForm1_BeginningEdit(object sender,
System.ComponentModel.CancelEventArgs e) {
_editing = true;
}
protected void DataForm1_EditEnded(object sender,
DataFormEditEndedEventArgs e) {
_editing = false;
}
void TreeView1_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
if (_editing) {
object previous = DataForm1.SelectedItem;
object current = TreeView1.SelectedItem;
if (MessageBox.Show("Are you sure you want to cancel the changes?",
"Confirm", MessageBoxbutton.OKCancel) == MessageBoxResult.OK) {
DataForm1.CancelEdit();
}
else {
TreeView1.SelectedItem = previous;
}
}
}
Have you tried to set AutoCommit at True ?

Silverlight - rebinding to a property?

I'm just getting started with silverlight.
Basically I have a silverlight user control that has various dataGrids and a combobox, their item sources set to the properties of a custom plain c# object.
My problem is that I have a dropdown list that when a user selects an item from the list a new row should appear in one of the grids.
All I'm doing is handling the SelectionChanged event and adding a new item to to a list in my custom object and setting the itemsource for the grid again. This doesnt seem to work; no row is added to the dataGrid
I have no idea how to force my grid to "rebind" to this property.
I've been reading about dependency properties, are these what I need?
Any pointers would be really appreciated.
The list you are binding against should be of the type ObservableCollection. Then the datagrid should display the new item automatically .
The problem is that when you assign the same List to the ItemsSource the DataGrid knows its the same List so it does nothing.
As Henrik points out you should expose an Observable<T> not a List<T> for properties that are to be bound to ItemsSource properties of multi-item controls such as DataGrid, ListBox etc.
In addition your "plain c# objects" should implement the INotifyPropertyChanged interface if you want changes made by code to these properties to automatically appear in the UI.
What you probably want to do is update the binding source - which is relatively easily done.
private void ComboBox_SelectionChanged(object sender, RoutedEventArgs e)
{
this.dataGrid.GetBindingExpression(DataGrid.ItemsSource).UpdateSource();
}
This is a tad hack-y but will do what you need it to do. Implementing INotifyPropertyChanged is another great suggestion.
Silverlight show have some great info on INotifyPropertyChanged here

What's the best way to auto-scroll a list view to the last added item?

I use a ListView to show a list of errors as they occur in my application. It behaves and looks exactly like the Error List in Visual Studio. I want to add auto-scrolling when the last error item is selected (like how Visual Studio's Log Window auto-scrolls when you place the caret at the end).
The list of errors is in an ObservableCollection, which is passed to the ListView.ItemsSource like this:
public ObservableCollection<ErrorListItem> Items;
...
MyListView.ItemsSource = _Items;
I tried performing the auto-scroll in the _Items_CollectionChanged event handler, but because this is the event on the ItemsSource and not on the actual ListViewItems, it's a pain to figure out if the last item is selected, select the new row, etc. It's especially hard since it seems the ListViewItems are not created instantly. I managed to make it auto-scroll by delaying the call to set the last item selected like this:
void _Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// determine the last item to select from 'e'
...
_ItemPendingToBeScrolled = newItemToSelect;
ListView.SelectedItem = newItemToSelect;
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(ThreadStart)delegate
{
if (_ItemPendingToBeScrolled != null)
{
ListView.ScrollIntoView(_ItemPendingToBeScrolled);
ItemPendingToBeScrolled = null;
}
})
}
But that's obviously not the right way to do it. Also, I want things to keep working if the list is filtered (not checking the last item in my source, but the last ListViewItem in the ListView).
Is there a way to listen to events when a ListViewItem gets added to the ListView following an addition to the bound collection? That would be the ideal event to capture in order to properly do my auto-scrolling. Or is there another technique I could use?
I have a lot of issues with listboxes/listviews and their scrolling, however, you mentioned hooking to the listview's changed event, is it because you can't listen to the observable collection's CollectionChanged event? ObservableCollection is way more stable than List controls, and you'll get the same notifications.
You can also bubble these events up if it's not working in the UI and you don't have access, this way you treat your scrolling in the UI without having access to the actual collection, just keep a reference to the Selected Item in your custom EventArgs class

Resources