Question:
How do send data to a view model when using command binding? So that, for example, when i click a button, it sends the "currently selected index" of a list so that it can perform an operation on that item of the list
Further Information:
I'm working on a program where i have a list of shipments, and each shipment has a list of pallets. I want to make a button that will allow me to add a new pallet to the currently selected shipment. >Edit> And to through another wrench into the works, each pallet has a list of products. so not only do i need to know what shipment i'm on, but i also need to know what pallet of what shipment I'm on.
When I do a command binding, I have no idea how to send the data to the ViewModel. I would like to keep this pure MVVM so i don't want to have the ViewModel checking the view for anything.
~N
Edits:
11/04/09 - I removed the section of the question about the instantiation of the ViewModel. I'll ask that again in a different question as this one is well on track for solving the other question. And I made a few other edits to the question to clearify in the direction i want. as well as changed some grammatical bits so that it wasn't talking about two questions when there is only one.
I usually expose a CollectionView from the view model and set the IsSynchronizedWithCurrentItem property on the ItemsControl displaying the list in the view. Then when the command is executed, I can inspect the CollectionView.CurrrentItem propety to see what is currently selected.
EDIT: This answer addresses the first question in your, um, question. Rather than your view sending the currently selected item to the ViewModel, the ViewModel keeps track of the currently selected item. So using this technique you don't need to work out how to send that information.
Something like this in your view model:
class ApplicationViewModel
{
// Exposes a list of ShipmentViewModels.
public CollectionView Shipments { get; private set; }
// A DelegateCommand or similar, that when executed calls AddPallet().
public ICommand AddPalletCommand { get; private set; }
void AddPallet()
{
ShipmentViewModel shipment = (ShipmentViewModel)Shipments.CurrentItem;
shipment.Pallets.Add(new PalletViewModel(...));
}
}
And then this in your xaml:
<ListBox ItemsSource="{Binding Shipments}" IsSynchronizedWithCurrentItem="True"/>
<Button Command="{Binding AddPalletCommand}>Add Pallet</Button>
This way you can also track the selection of the Shipments collection from your ViewModel and update the command's CanExecute state.
Does that help any?
For keeping track of the currently selected item I do something similar to Groky, maybe this example make a little more sense.
In your ViewModel that contains the collection that your list is bound to (I'm using a ListBox in this example) expose a property that relates to the selected item.
// Assuming your using the MVVM template from Microsoft
public class PalletListViewModel : ViewModelBase
{
// The collection our list is bound to
private ObservableCollection<Pallet> _palletList;
// The current selected item
private Pallet _selectedPallet;
// Our command bound to the button
private DelegateCommand _processCommand;
public ObservableCollection<Pallet> PalletList
{
get { return _palletList; }
}
public Pallet SelectedPallet
{
get { return _selectedPallet; }
set
{
if(value == _selectedPallet)
return;
_selectedPallet = value;
// INotifyPropertyChanged Method for updating the binding
OnPropertyChanged("SelectedPallet");
}
}
public ICommand ProcessCommand
{
get
{
if(_processCommand == null)
_processCommand = new DelegateCommand(Process);
return _processCommand;
}
}
private void Process()
{
// Process the SelectedPallet
}
}
<Window ...>
<Grid x:Name="LayoutRoot">
<Button Content="Process Pallet" Command="{Binding ProcessCommand}" />
<ListBox ItemsSource="{Binding PalletList}" SelectedItem="{Binding SelectedPallet}">
...
</ListBox>
</Grid>
</Window>
Hopefully this is what your looking for.
Related
I want to start using MVVM in my project so I have started to investigate it.
While I was playing a bit with WPF I've encountered a bug that I couldn't find a solution to him by myself and while exploring internet.
I have something like that(I can't paste my full code because its not in the same network):
MainView.Xaml
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<my:AddToInvitation />
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<my:RemoveFromInvitation />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
MainViewModel.cs
public ObservableCollection<PersonViewModel> Persons { get; set; }
public MainViewModel()
{
this.Persons = new ObservableCollection<PersonViewModel>();
for(int i=0;i<1000;i++)
{
PersonViewModel personVM = new PersonViewModel (string.Format("Person - {0}",i));
this.Persons.add(personVM);
}
}
PersonViewModel.cs
private Person PersonObject { get; set; }
public string Name
{
get
{
return this.PersonObject.Name;
}
}
public PersonViewModel(string personName)
{
this.PersonObject = new Person(personName);
}
Person.cs
public string Name { get; set; }
public Person(string name)
{
this.Name = name;
}
Now if you try to paste it and run it, it will look just fine.
The problem is when you try the following instructions:
1) Check the first 10 persons in the ListBox.
2) Scroll down the ListBox to the bottom of it.
3) Leave the mouse when the list box is scrolled down.
4) Scroll back up to the top of the ListBox.
5) Poof! you'r checking disappeared.
Now the solution i have found to this is to add IsChecked property(Though I don't really need it) to the PersonViewModel and bind it to the CheckBox IsChecked DependencyProperty, but then I have added a functionality that lets the user to press a button and it will iterate over all the persons in the ListBox and change it IsChecked property to true(Button -> Select all).
Following to the disappear Checks bug I have crossed another bug which I believe somehow is connected to the disappearing Checks - the actions that I have put to trigger when Check and Uncheck occurs would trigger only for some of the CheckBoxes when you select all.
I tried to count how many times the actions would happen when I used the select all function and I found a connection between the height of the ListBox(Current Visible CheckBoxes) and the amount of the triggers that fired, furthermore I scrolled to the middle of the ListBox and used the SelectAll functionality and the triggers didn't fire until the loop encountered the first visible ChekBox that I can see in my ListBox.
Its a bit hard to understand this bug if you don't try it, so please comment here only if you tried this.
Thanks in advance!
The simple answer is: You are going against the current.
The binding is all about changing value in your ViewModel and allowing you to write your code against simple view model classes so that your presentation logic is free of business logic. In your example the decision to execute AddToInvitation RemoveFromInvitation is in your view and it should not be there.
You will be good with bool IsInvited{get;set;} property that is easily bound to checkbox (no dependency property required). And this will allow user changes to be persisted in your view model. If you need some other more complicated logic you should attach to PropertyChagned event form INotifyPropertyChanged interface that your ViewModel must implement. Then you can change property in your simple class at will and ui will update accordingly.
I'll get this out of the way right off the bat... my base view model class is implementing INotifyPropertyChanged. Here's the scenario:
I have a single view with a single view model. The view is a master/detail with the master being a list box of Game objects that I'm populating without issue. When a Game object is selected in the master list box, I want to populate some details in various controls. The control that's causing me problems is a combo box.
Now, the combobox is being populated using a collection of Team objects. Each Game object has a "Team" object and once the combobox is populated, I want to select the appropriate Team object in the combobox that the Game object specifies.
Now, I know this is working to some degree because if I do the same binding to a textbox, the right information appears (I can get the bound Team object to appear in the textbox, but I can't get it to select from the list).
I'm seriously lost, been working on this for a few hours now. Can anyone assist?
Edit: I have a feeling this has something to do with the object references. But wouldn't SelectedValue still work?
ViewModel:
public ObservableCollection<Team> Teams
{
get { return this.teams; }
set
{
this.teams = value;
OnPorpertyChanged("Teams");
}
}
public Team SelectedTeam
{
get { return this.selectedTeam; }
set
{
this.selectedTeam = value;
OnPorpertyChanged("SelectedTeam");
}
}
private ObservableCollection<Team> teams;
private Team selectedTeam;
Team Class:
public class Team
{
public string Name { get; set; }
}
View:
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Teams}"
SelectedItem="{Binding Mode=OneWayToSource, Path=SelectedTeam}" />
EDIT: The issue underneath is fixed, GO TO EDIT2 in this post.
I have an Organisation entity and a Region entity. An object of type Organisation can have one or more Region objects connected to it, thus I have a foreign key in my Region entity to the Organisation Entity. The Organisation and Region objects are pulled from my database using WCF RIA and entity framework. I want to put the Organisation objects in one ComboBox and the Region objects in another ComboBox, and when selecting an organsation having the ComboBox for Region objects automatically only showing regions that are connected to the selected organisation. Should be pretty basic, but the way I've designed it right now it doesnt work at all.
So, any hint to how I can achive this? A simple simple codeexample is much appreciated!
(I'm using SL4,WCF RIA MVVM)
EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2:
Using Venomo's ElemntBinding answer this is working great for me now when I want to add a new object to my collection, and I'm just pulling the avaible countries and connected regions and then type a site in a textbox...So I get my combination of Organisation, region and site in my database :)
Now, I've got a new problem when I want to EDIT a site in my collection. In EDIT mode, I want the two dropdowns to be preselected and disabled (BusinessRule is that I can edit the sitename, not which organisation og region it's connected to). So by setting the SelectedIndex property on Organisation combobox I get my organisation selected, but when doing the same on the Regions combobox it fails with an Object Reference error.
You can achieve this with some clever ElementBindings.
Basic example:
Let's say we have a simple class like this:
public class Country
{
public string Name { get; set; }
public IEnumerable<string> Regions { get; set; }
}
Then, we'll have two ComboBoxes: one for choosing a country and another for choosing a region in that country. The second one should update itself when the value of the first one changes.
Okay, first we have to tell Silverlight how to display a Country. For complex scenarios we could use a DataTemplate for that, but for now, we will only need the DisplayMemberPath property of the ComboBox class.
<ComboBox x:Name="cbCountries" DisplayMemberPath="Name"/>
So, we create a simple collection of these objects in the code behind:
var countries = new List<Country>()
{
new Country
{
Name = "USA",
Regions = new List<string>
{
"Texas", "New York", "Florida", ...
},
},
new Country
{
Name = "UK",
Regions = new List<string>
{
"Scotland", "Wales", "England", ...
},
},
...
};
I know that those are not all of the regions in the example countries, but this is a Silverlight example and not a geographical lesson.
Now, we have to set the ItemsSource of the ComboBox to this collection.
cbCountries.ItemsSource = countries;
Both of these can be in the constructor in the code-behind.
Okay, now back to XAML!
We'll need another ComboBox and a way for telling it to get its items from the other collection dynamically.
Binding its ItemsSource to the other ComboBox's selected item is just the most obvious way to achieve that.
<ComboBox x:Name="cbRegion" ItemsSource="{Binding ElementName=cbCountries, Path=SelectedItem.Regions}"/>
This should do the trick quite simply.
If you use MVVM:
You can bind to the ItemsSource of the first ComboBox from the ViewModel. The rest remains the same.
To tell what the selected values are to the ViewModel, use Two-way bindings on the SelectedItem property of both ComboBoxes and bind that to whatever property you have for it in the ViewModel.
If your collection can change dynamically:
If the list of the countries (or whatever it is that you want to use this for) can change during runtime, it is best if you implement INotifyPropertyChanged for the Country class and for the regions, use ObservableCollection<>.
If it doesn't need to change during runtime, no need to bother with these.
View Model:
public ObservableCollection<Organisation> Organisations { get; set; }
private Organisation selectedOrganisation;
public Organisation SelectedOrganisation
{
get { return this.selectedOrganisation; }
set
{
if (this.selectedOrganisation != value)
{
this.selectedOrganisation = value;
this.NotifyPropertyChanged("SelectedOrganisation");
this.UpdateRegions();
}
}
private IEnumerable<Region> regions;
public IEnumerable<Region> Regions
{
get { return this.regions; }
set
{
if (this.regions != value)
{
this.regions = value;
this.NotifyPropertyChanged("Regions");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void UpdateRegions()
{
// If regions are not pre-populated you might need to perform a new call to
// the service that retrieves organisations in order to retrieve the associated Regions entities
// for the SelectedOrganisation organisation
this.Regions = this.SelectedOrganisation.Regions;
}
In your View:
<ComboBox x:Name="Organisations" ItemsSource="{Binding Organisations}" SelectedItem="{Binding SelectedOrganisation, Mode=TwoWay}" />
<ComboBox x:Name="Regions" ItemsSource="{Binding Regions}" />
I have a simple WPF program using the Master-Detail UI pattern, where the Detail shows the currently selected item of a collection in the Master pane. I'm using MVVM, and each XAML page is backed by a ViewModel object which is set as it's DataContext.
Now, I want to add a DELETE button in the Master pane to delete from the master list of items. However, I can't figure out how to pass the viewmodel object of the currently selected item as a button CommandParameter to the routed command handler code.
Thanks in advance for any pointers.
Mike
Something similiar to what Paul has shown is where your view model will know which item is currently selected. I.e.
public class MyVM
{
public ObservableCollection<MyObject> MyCollection { get; set; }
public MyObject CurrentItem { get; set; }
}
Your XAML can then be simply
CommandParameter="{Binding Path=CurrentItem}"
As long as your master pane sets the CurrentItem property when you select it, your command can simple have the CurrentItem set as the command parameter.
One option would be to create each command with a reference to the view model, and create a property on the view model which is bound to the currently selected item. This way you don't need to pass the selected item as a parameter - the command can retrieve it from the VM. If this isn't suitable for your circumstances, you could pass the selected item something like this:
<Button Content="Delete"
Command="{Binding DeleteCommand}"
CommandParameter="{Binding ElementName=listBox_, Path=SelectedValue}" />
Where listBox_ is a control which derives from Selector.
Hope that helps,
Paul
I am trying set up databinding as described in the title.
The problem I having is binding to a generic list.
Any examples out there.
I can't use BindingListCollectionView on a generic list so have to use
CollectionView.
The issue I am puzzled about is to add a new item
on click of Add button I add a new item to the
generic list and refresh the View. But if user
does not follow through the list now has empty
item.
I know this is basic but how is that handled normally?
Malcolm
I see two questions here and I'll try to answer them step by step.
Binding list of items with detail view
Given these ViewModel classes (imagine everyone implements INotifyPRopertyChanged):
public class DataView {
public Item SelectedItem {get; set; }
public List<Item> Items { get; private set; }
}
public class Item {
public string Title { get; set; }
}
Putting a Data instance into the DataContext, a minimal View might look like this:
<StackPanel>
<ListView Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBox Text="{Binding SelectedItem.SelectedItem.Title}" />
</StackPanel>
Adding new items
To be able to create a new Item without immediately adding it to the list, you might want to split off the newly created object into its own area. Visually you can either have it in a new pop up or integrated into the list, but actually it'll be only added to the list on the next try to add or acknowledge the parent dialog. At this point you can also verify whether the Item is valid enough to add it to the list.