Implement 'Delete Item' command for multiple ListView controls - wpf

I have multiple ListViews in an MVVM WPF application, backed by ObservableCollections.
I'm implementing a "Delete Item" context menu item for each ListView. Currently I have the SelectedItem for each ListView bound to the same object in my ViewModel. However to delete the item from the ObservableCollection requires the name of the ListView (in this case Wk01CECollection):
private void DeleteLO()
{
this.Wk01CECollection.Remove(SelectedCE);
}
Is there a way to reference the ListView that the SelectedItem is a member of? As it is I'll need to wire up a separate delete method for each ListView.

I'm sure this is not the "best practice" way to achieve this, but it's quite simple:
You could pass the ListView's selected item as a parameter to the command:
<Button Content="Delete selected item" Command="{Binding DeleteCommand}" CommandParamenter="{Binding ElementName=SomeListView, Path=SelecteItem}" />
Since you can try to remove an item from an ObservableCollection even if it does not exist in the collection without getting an exception, in your private Delete method you can try to remove that item from all the ObservableCollections you have in your ViewModel. I'm assuming here that an item cannot be in two collections at the same time, if this is not the case, what I said won't work because you'll remove the item from all your collections. If you plan to do this, just check for null before removing, if you try to remove a null object you'll get an exception.
private void DeleteLO(object listitem)
{
if(listitem != null)
{
if(listitem as CollectionType1 != null) //cast your listitem to the appropiate type inside your collection
this.Wk01CECollection.Remove(listitem);
if(listitem as CollectionType2 != null)
this.Wk02CECollection.Remove(listitem);
//etc.
}
}
Besides all of this, if you are using MVVM it is best that the ViewModel does not know about the View, so referencing the ListView inside the VM would break that principle.

I understand the issue is long gone, but will post for googlers.
This worked for me:
private void LvPrevKeyDown(object sender, KeyEventArgs e)
{
//Nothing to do here
if (Lv.SelectedItems.Count == 0) return;
//Empty space for other key combinations
//Let's remove items. If it's a simple delete key so we'll remove just the selected items.
if (e.Key != Key.Delete || Keyboard.Modifiers != ModifierKeys.None) return;
var tmp = Lv.SelectedItems.Cast<ColorItem>().ToList();
foreach (var colorItem in tmp)
{
_cList.Remove(colorItem);
}
}
Nothing fancy in the xaml. Just some columns bound to _cList properties and items source bound to _cList, of course.
Hope it'll help someone!
Kind regards!

Related

Object reference not set to an instance of an object when creating two ComboboxItems functions in wpf

I´m all out of ideas here
The thing is that Im using two comboboxes and I want to get values from both comboboxes to show content in DataGrid in wpf.
I have this function that gets values from both comboboxes. This works well.
private void cboxYearChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
But then I want to create another function to check for changes on the other combobox:
private void cboxMonthChange(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem;
string valueYear = typeItemYear.Content.ToString();
ComboBoxItem typeItemMonth = (ComboBoxItem)comboBox1.SelectedItem;
string valueMonth = typeItemMonth.Content.ToString();
}
I can build, but when I run this I get the Object reference not set to an instance of an object error on the ComboBoxItem typeItemYear = (ComboBoxItem)comboBox2.SelectedItem; line in the cboxMonthChange function
What am I missing here ?
SelectedItem is null until something is selected. Unless they both change at the same time (which is not possible as these events are fired in sequence), either the type cast on comboBox1.SelectedItem or comboBox2.SelectedItem will throw an exception.
Check if SelectedItem is set the methods.
Or use another cast, like:
ComboBoxItem item1 = comboBox1.SelectedItem as ComboBoxItem;
if (item1 != null)
{
// do something
}
Hope this helps :-)
1) you should not refer to control's name within the code whenever possible.
So you can know, for instance, which ComboBox was changed within a SelectionChanged
handler by casting the Sender to a ComboBox.
2) but in such a simple case, just use public properties and bind them to
your ComboBox : all will get done with no code.
<ComboBox x:Name="YearSelectCB" SelectedItem="{Binding SelectedYear}">
<ComboBox x:Name="MonthSelectCB" SelectedItem="{Binding SelectedMonth}">
(you can set the DataContext of the window in several ways, for instance in the
window loaded event handler (DataContext=this) )

how to add\remove multiple items from one listview to another in MVVM using 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

WPF Datagrid ignores SelectedItem changes if the item is not in the grid

I have an ObservableCollection of "things" in my view model, and a couple filtered subsets of that list in additonal ObservableCollections. I have two DataGrids on the screen, and I have bound them each to one of the subset ObservableCollections.
Both DataGrids have their SelectedItem property bound to a SelectedThing property in the view model.
When I change SelectedThing either programatically or by selecting a row in one of the two grids, it will change as expected. If the item now pointed to by SelectedThing exists in a grid, the grid will update it's selected item.
So here is my problem... if SelectedThing does not exist in the grid's ItemSource, the selection acts like nothing happened and remains in whatever state it was in before SelectedThing was changed. Ideally I would like the selected to Clear if the underlying view model property no longer is set to something in the grid's ItemsSource... anyone have any suggestions?
Ok. Got it working. In case it helps someone else in the future, here's what made it work...
In your code behind, register an event handler for the view model's PropertyChanged event, and then use that to check each grid to see if it contains the item being selected. If not, then clear the selected in that grid. I also modified my SelectedThing property to ignore incoming NULL values to avoid a deadlock (and in my app it will never be NULL after initialization)
_vm is a Property that returns my view model.
_vm.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_vm_PropertyChanged);
void _vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedThing")
{
CheckSelection(grid1, _vm.SelectedThing);
CheckSelection(grid2, _vm.SelectedThing);
}
}
void CheckSelection(DataGrid grid, object selectedItem)
{
if (grid.ItemsSource != null)
{
bool itemInGrid = false;
foreach (var item in grid.ItemsSource)
{
if (item == selectedItem)
{
itemInGrid = true;
break;
}
}
if (!itemInGrid) // clear selection
{
grid.SelectedItem = null;
// not sure why, but this causes the highlight to clear. Doesn't work otherwise
grid.IsEnabled = false;
grid.IsEnabled = true;
}
}
}

ItemsSource and collections in which only element properties change

I'm having grief with a ComboBox not reflecting changes in the properties of the collection to which its ItemsSource is bound.
There is a tree made up of a Categories observable collection of Category objects containing Setting objects. Some settings define the presentation names for the domain of values permitted for other settings. These are spread over several categories, but a little magic with LINQ produces an ObservableCollection in which ChannelCategory exposes properties ChannelNumber and ChannelTitle.
ChannelCategory.ChannelTitle is a StringSetting, ChannelNumber is an int, an immutable identity value for which ChannelTitle provides a display string. Omitting abundant but irrelevant other nodes, we have the following:
Channels
ChannelCategory
ChannelNumber = 1
ChannelTitle = "caption for channel one"
ChannelCategory
ChannelNumber = 2
ChannelTitle = "caption for channel two"
ChannelCategory
ChannelNumber = 3
ChannelTitle = "caption for channel three"
...
This Channels collection is prepared and exposed by a property on a class instantiated and added to the window resource dictionary in XAML (accessible as a StaticResource). This arrangement allows me declaratively bind a combobox to Channels
<ComboBox VerticalAlignment="Center" Grid.Column="2"
ItemsSource="{Binding Source={StaticResource cats}, Path=Channels}"
DisplayMemberPath="ChannelTitle.Editor"
SelectedValuePath="ChannelNumber"
SelectedValue="{Binding Editor}"
/>
This all works, but edits elsewhere to the ChannelTitle value are not reflected in the values shown in the combobox list.
Various debugging trickery with breakpoints and the DropDownOpened event allowed me to ascertain that the updates are available from the collection referenced by ItemsSource.
And finally we reach the mystery. Why doesn't the combobox detect changes to the elements of the collection? The collection itself is an ObservableCollection so it should notify property changes.
The elements of the collection are all ChannelCategory which is a DependencyObject, and ChannelCategory.ChannelTitle is a DependencyProperty.
I think the problem is that I'm neither adding nor removing items from the collection, so as far as the collection is concerned it has the same elements and therefore hasn't changed.
Can anyone suggest a strategy for causing changes to ChannelTitle to cause Channels collection to notify change so the combobox updates?
Rachel's suggestion ended up as shown below. In the context of my application there was considerably more complexity because each ChannelCategory owns a collection of settings objects, so the changing value is a property of a an object in a collection that is a property of an object in the collection to which ItemsSource is bound. But the essence of Rachel's suggestion simply needed application at two levels.
public class ObservableChannelCollection : ObservableCollection<ChannelCategory>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (ChannelCategory channel in e.NewItems)
channel.PropertyChanged += channel_PropertyChanged;
if (e.OldItems != null)
foreach (ChannelCategory channel in e.OldItems)
channel.PropertyChanged -= channel_PropertyChanged;
base.OnCollectionChanged(e);
}
void channel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
}
}
ObservableCollection tracks changes to the Collection itself, such as Add, Remove, Reset, etc, but it does not track changes to individual items inside the collection. So if you update the property on an item in an ObservableCollection, the collection doesn't get notified that something has changed.
One alternative is to add an event to the ObservableCollection.CollectionChanged event, and when new items get added, hook up a property change on the new items that will raise the collection changed event.
void MyObservableCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(MyItem item in e.NewItems)
{
MyItem.PropertyChanged += MyItem_PropertyChanged;
}
}
if (e.OldItems!= null)
{
foreach(MyItem item in e.OldItems)
{
MyItem.PropertyChanged -= MyItem_PropertyChanged;
}
}
}
void MyItem_PropertyChanged(object sender, PropertyChange e)
{
RaisePropertyChanged("MyObservableCollection");
}

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 ?

Resources