So I've got a listview and it's itemsSource property bound to an ObservableCollection, placed on the view model. And a button the on view
How do I make it so the button deletes the item, selected on the listview, from the observableCollection?
Just to add another way to do it:
<ListView ItemsSource="{Binding MyList}"
SelectedItem="{Binding SelectedItem}"/>
<Button Command="{Binding DeleteCommand}"/>
In your ViewModel you have a property called SelectedItem that will be updated every time you change the selection in the ListView.
Now you can handle deletion in the ViewModel easily:
internal class ViewModel
{
public ViewModel()
{
this.DeleteCommand = new RelayCommand(() => this.Delete());
}
public void Delete()
{
this.MyList.Remove(this.SelectedItem);
}
}
Go HERE for further information about RelayCommand
There are many ways to do this. One way is to create a RelayCommand or a DelegateCommand with Parameter
<Button Command="{Binding MyDeleteCommand}"
CommandParameter="{Binding ElementName=mylistview, Path=SelectedItem}"/>
You can go the way AlSki posted and bind the SelectedItem to your ViewModel and handle the command without parameter in your ViewModel
Bind a second property to the list views selected item, and a third to a Command on the view model, that simply removes the selected from the list of items.
See http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Related
I have a WPF DataGrid in a Window with associated View(*.xaml.cs) and ViewModel that is successfully executing a bunch of functionality. However, functions that modify items instead of altering the collection do not update until a sort, resize, etc.
I've found a bunch of search results suggesting the solution is to make the item type implement INotifyPropertyChanged and add/subtract event handlers as appropriate for every item in the collection. I tried for a bit using those examples without success, but frankly it doesn't seem like a great option.
The item type is declared elsewhere in the app and is sharing the object instances with other modules, so I would like not to modify that class. It also seems like a poor design to tie the implementation of the DataGrid's ItemsSource to the item type within; the list container is already an ObservableCollection invoking OnPropertyChanged as needed already, so why should that not be sufficient?
I'm able to update via DataGrid.Items.Refresh() - which unfortunately does not seem to have an overload for specific items/properties instead of updating the entire list, but that's a minor issue - but only my View has a reference to the DataGrid itself (per MVVM), whereas the Command binding is in the ViewModel.
I would actually like to put those Command bindings in the View, and I don't understand why convention is to put those in the VM and thereby bypass the View during a UI event. For example, to delete items I can select them and either press Delete (KeyUp handler is in the View, which then passes selected items as a list to the VM) or select Delete in the context menu (Binding is to an ICommand in the VM, which routes to the same function invoked by the View). Why would it not be more desirable to bind both to the View's event handler (or two handlers both in the View)?
I've seen some results that use a RelativeSource for the Command binding to an ancestor of type UserControl...I've tried with type Window to try to bind to the View's method for naught. As of now my best option is to put a delegate event RefreshListItems on the VM and subscribe to it from the view with a function that invokes DataGrid.Items.Refresh().
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel, and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
Edit per mm8's suggestion:
I tried your code for the ICommand in my view, but I get this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=SetService; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
That's with each of these attempts, with and without CommandParameter, bound to an ICommand of type either RelayCommand or DelegateCommand:
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
.xaml:
<Window>
…
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
…
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
…
</Window>
.xaml.cs:
public partial class TheGridView : Window
{
TheGridViewModel _viewModel;
public ICommand SetService;
[ImportingConstructor]
public TheGridView(TheGridViewModel vm)
{
DataContext = _viewModel = vm;
vm.RefreshListItems += () => TheGrid.Items.Refresh();
InitializeComponent();
Closing += Window_Closing;
SetService = new RelayCommand(SetSvc);
}
private void SetSvc(object selectedItem)
{
// Doesn't get here
}
}
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel?
There is nothing that stops you from defining ICommand properties in the code-behind of the view and bind to them like this (assuming your view is a Window):
Command="{Binding YourCommandProperty, RelativeSource={RelativeSource AncestorType=Window}}"
Edit:
A ContextMenu resides in its own visual tree but you should be able to bind to the parent window through the Tag property of the DataGrid, something like this:
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
<DataGrid.Tag>
<Binding Path="." RelativeSource="{RelativeSource AncestorType=Window}" />
</DataGrid.Tag>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding PlacementTarget.Tag.SetService, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
...and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
You can't do this unless you refresh the entire control (for example using DataGrid.Items.Refresh()). That's why you should implement INotifyPropertyChanged.
If you currently bind to some class that is shared across several modules and you don't want to modify this class, you could create a new client-specifc wrapper class that does implement INotifyPropertyChanged and bind to this one instead of binding to the common class, e.g.:
public class Wrapper : INotifyPropertyChanged
{
private readonly SharedModel _model;
public Wrapper(SharedModel model)
{
_model = model;
}
private string _property;
public string MyProperty
{
get { return _property; }
set { _property = value; OnPropertyChanged(); }
}
//...
public event PropertyChangedEventHandler PropertyChanged;
}
Xaml:
<ComboBox Grid.Row="1"
Name="CmbClasse"
Grid.Column="3"
Margin="5,5,5,5"
ItemsSource="{Binding DataContext.Classeses}"
SelectedValuePath="ClasseId"
DisplayMemberPath="ClasseName"
SelectedValue="{Binding DataContext.CurrentSelectedPersonagem.IdClasse, Mode=TwoWay}"/>
When I open my application, the ComboBox do not have display any default item. But the item list is working, have all the items on it. Please help, if a image is needed just ask.
Thanks.
Here is a method I use all the time.
Firstly, your View Model should have two properties, one to represent the collection, and the other to represent the selected item.
public IEnumerable<YourObject> MyObjects { get; private set; }
public YourObject SelectedObject { get; private set; }
Note: It would be a good idea to use an ObservableCollection for MyObjects, and also implement INotifyPropertyChanged. Use this only if you need to create brand new instances of your collection in the View Model.
Your ComboBox should then bind to these properties, like so:
<ComboBox ItemsSource="{Binding MyObjects}"
SelectedItem="{Binding SelectedObject}"
...
/>
Now, here comes the magic. When you create your collection, simply set the SelectedObject to be the FirstOrDefault item in the list.
private void LoadData()
{
//TODO: Load the data into the collection
//Set the first item
this.SelectedObject = MyObjects.FirstOrDefault();
}
This will ensure that the first item is always selected. You do not need to implement INotifyPropertyChanged on the SelectedObject property unless you are planning on manually changing the value of this property in your View Model.
I have a simple WPF page with a custom control (BrowsingPanel) containing a ListBox, and another control (ItemDataSheet) which displays data related to the element which is selected in the ListBox. When I click on an item in the ListBox, a command is sent to the BrowsingPanelViewModel, which sends a message. The message is received by the ItemDataSheetViewModel, which updates the ItemDataSheet view.
This is my BrowsingPanel.xaml:
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
Background="DarkGray">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"
CommandParameter="{Binding ElementName=itemsList, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
It works well, except that I would like the first ListBox item to be selected by default. To do so, I've tried two things:
First, I've tried to select the first item in the BrowsingPanelViewModel's constructor as shown below.
public RelayCommand<MyItem> SelectedItemChangedCommand { get; private set; }
public BrowsingPanelViewModel()
{
SelectedItemChangedCommand = new RelayCommand<MyItem>(SelectedItemChanged);
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(ParsetemFromResourceName("Resources/toto.txt"));
MyItems.Add(ParseItemFromResourceName("Resources/tata.txt"));
MyItems.Add(ParseItemFromResourceName("Resources/titi.txt"));
//Select the first item if there's one
if (MyItems.Any())
SelectedItemChanged(MyItems.First());
}
void SelectedItemChanged(MyItem selectedItem)
{
Messenger.Default.Send(new NotificationMessage<MyItem>(selectedItem, Notification.SelectedMyChanged));
}
This works fine, the ItemDataSheetViewModel displays the data corresponding to this item, but the item is not (visually) selected in the ListBox.
Then, I've tried to select the first item from the BrowsingPanel view. In the code behind, I have a handler for itemsList_Loaded which looks like this:
private void itemsList_Loaded(object sender, RoutedEventArgs e)
{
//Select the first item by default
itemsList.Focus();
if (itemsList.Items.Count > 0)
itemsList.SelectedItem = itemsList.Items[0];
}
And this is where I get a weird behavior. This selects the item correctly in the ListBox, but the SelectedItemChanged command is not triggered. And I don't understand why.
The funny part is that if I replace my EventTrigger with a SelectionChanged event that I put in the code behind as shown below, then the callback function is called.
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
Background="DarkGray"
Loaded="itemsList_Loaded"
SelectionChanged="itemsList_SelectionChanged"> <!-- This is called when changing SelectedItem in the Loaded -->
</ListBox>
</Grid>
Obviously, by combining the 2 solutions I have mentioned, it works: the bit in the view model constructor displays the appropriate data in the ItemDataSheet view, while the bit in the itemsList_Loaded visually selects the item in the List. But I don't find this very elegant...
It seems to me that programmatically changing the ListBox's SelectedIndex should trigger the SelectionChanged command, but it doesn't.
Any help will be appreciated!
Bare in mind this is a solution for a single select listbox.
You really don't need most of that code.
It can be as simple as only needing the SelectedValue property on the listbox:
<Grid>
<ListBox x:Name="itemsList"
ItemsSource="{Binding MyItems}"
SelectedValue="{Binding Path=MySelectedItem, Mode=TwoWay}"
Background="DarkGray">
</ListBox>
</Grid>
This can then be bound to your BrowsingPanelViewModel with the MySelectedItem property:
private MyItem m_MySelectedItem;
public MyItem MySelectedItem
{
get
{
return m_MySelectedItem;
}
set
{
m_MySelectedItem = value;
NotifyPropertyChanged("MySelectedItem");
}
}
The notifypropertychanged in the setter is key here.
You can then from your viewmodel select the first list item by assigning this property.
You will also need a DataTemplate for your MyItem object in the scope of your ListBox which can be as simple as:
<DataTemplate DataType={x:Type MyItem}>
<Textblock Text="{Binding Path=MyItemDescription"/>
</DataTemplate>
Or whatever.
I have an MVVM Application and want to add a ContextMenu.
I added the ContextMenu to XAML and then set the Items like this (only one item here because it doesn'T matter):
<MenuItem Header="{x:Static Monitor:MonitorResources.R0206_SaveLatestValueToDatabase}"
IsCheckable="true"
IsChecked="{Binding ElementName=root, Path=Model.SaveToDbOneChecked}"
IsEnabled="{Binding ElementName=root, Path=Model.SaveToDbOneEnabled}">
The SaveToDbOneChecked and SaveToDbOneEnabled are Properties in my Model which are implemented like this:
private bool mSaveToDbOneEnabled;
public bool SaveToDbOneChecked
{
get { return mSaveToDbOneChecked; }
set { mSaveToDbOneChecked = value; OnPropertyChanged("SaveToDbOneChecked"); }
}
I set these before the ContextMenu gets called on the SelectionChanged in the GridView the ContextMenu is in. But it won't show the Checked sign next to the text of the MenuItem although the SaveToDbOneChecked has been set to true! I don'T know where i do something wrong and hope that somebody can help me here.
A few things you have to do to make this work. First of all you cannot bind from inside a MenuItem using ElementName property since the target element is most often out of your scope.
If I understand correctly the Model is your ViewModel property, in this case all you have to do is to set it as the DataContext of the Element on which the ContextMenu is placed.
This will set the same DataContext for your MenuItem and you can bind directly to DataContext:
IsChecked="{Binding SaveToDbOneChecked, Mode=TwoWay}"
I have a list view that is populated from a mysql table, I want to be able to double click on a item in the listview and bring up a new window with more information. How do I pass the first column value to the new window (this is the id of the item)? This way I can make another query to get the rest of the info about the item.
Was having a similar issue with a ListBox wanting to open a window (Different View) with the SelectedItem as the context (in my case, so I can edit it).
The three options I've found are:
1. Code Behind
2. Using Attached Behaviors
3. Using Blend's i:Interaction and EventToCommand using MVVM-Light.
I went with the 3rd option, and it looks something along these lines:
<ListBox x:Name="You_Need_This_Name"
ItemsSource="{Binding Your_Collection_Name_Here}"
SelectedItem="{Binding Your_Property_Name_Here, UpdateSourceTrigger=PropertyChanged}"
... rest of your needed stuff here ...
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Command:EventToCommand Command="{Binding Your_Command_Name_Here}"
CommandParameter="{Binding ElementName=You_Need_This_Name,Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
That's about it ... when you double click on the item you want, your method on the ViewModel will be called with the SelectedItem as parameter, and you can do whatever you want there :)
What is meant with MVVM is that you will have for example a ViewModel containing a property SelectedThing bound to the SelectedItem of the listview and a command that gets executed using EventCommand on the MouseDoubleClick event of the View which will execute in the end the operation you want on the SelectedThing which could be also passed in as parameter to the command also by binding.