Data binding a WPF ComboBox from a ViewModel - can't change selected item - wpf

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

Related

how to bind autocomplete box to a database table column?

i am currently creating a Gridview using telerik control which displays data from the sql database which i displays through domain datasource used in wcf ria.(ADO.net entity model etc)
i want to add an autocomplete box above my radgrid where i type an name and other matchable entries are also listed.
when i click on the entry then radgrid may display whole row containing that name.
i am using silverlight 4,wcf ria,telerik controls.
please provide a sample coding idea in xaml and xaml.cs.
i tries to access telerik demos but they are not running on my system.
As an example... say you have a list of Customers, of which you want to display their names in your AutoComplete box. Further, your Grid should display all customers, and when a Name is selected in the AutoComplete box, the Selected item of the grid displays.
What you need to do is bind the SelectedItem property of the RadGridView & AutoCompleteBox. What I would do is bind the AutoCompleteBox to a property named SelectedName, like so:
<input:AutoCompleteBox ItemsSource="{Binding Names}" SelectedItem="{Binding SelectedName, Mode=TwoWay}" />
Emphasis on the 'Mode=TwoWay' - this is what will alert your code behind that the UI has changed.
In your code behind, you would create properties like this:
private string selectedName;
public string SelectedName
{
get { return selectedName; }
set
{
if (value != null)
{
var query = (from c in CustomersList
where (c.Name == value)
select c).FirstOrDefault();
SelectedCustomer = (Customer)query;
selectedName = value;
}
}
}
Notice how, when you're setting the SelectedName, you're using LINQ to determine which of the customers were selected. One pitfall here would be if you have multiple names in a list... this code only selects the first. If this is an issue, you probably should rethink your architecture..
Then for your grid, you would bind the SelectedItem like so:
<telerik:RadGridView
....
SelectedItem={Binding SelectedCustomer, Mode=TwoWay"}
....
</telerik:RadGridView>
In your code behind, you'd create this property:
private Customers selectedCustomer;
public Customers SelectedCustomer
{
get { return selectedCustomer; }
set {
selectedCustomer = value;
MyGridView.SelectedItem = selectedCustomer;
}
}
Something like that should get you started.
SS

Inject views into ItemsControl depending on object type

I have a service returning an array of type Party. Party has two subtypes, Person and Organization. I’m consuming this service in my WPF application (Prism, MVVM) from a view model. In the constructor of this view model I populate an observable collection of type Party:
public PhoneBookViewModel(IPhoneBookService phoneBookProxy)
{
_phoneBookProxy = phoneBookProxy;
var parties = _phoneBookProxy.GetAllParties();
_parties = new ObservableCollection<Party>(parties.ToList());
}
So far so good. In my PhoneBookView I have an ItemsControl that binds to this collection. In this control I want to render each Party by using another View (and its view model). So when Party is of type Person, inject PersonView and pass the Party object to the constructor of the PersonViewModel, and when Party is of type Organization, render OrganizationView, and so on... You get the picture (or?).
But I can't figure out how to do this in XAML. Any ideas?
This is probaly not the best way of doing it, so if you can recommend a better approach, please enlighten me :-)
Thanks!
Lets examine this from the view towards the model:
Lets assume we have 2 different types of views, 1 type of view model:
ViewA --> Created within an items control using DataTempate/DataTemplateSelector, Binded > to ViewModelA
ViewB --> Created within an items control using DataTempate/DataTemplateSelector, Binded to ViewModelA
If both views are binded to the same view model, you would end up with the same view.
Lets try again with 2 different types of views and 2 different types of view models:
ViewA --> Created within an items control using DataTempate/DataTemplateSelector, Binded to ViewModelA --> Binded to ModelA
ViewB --> Created within an items control using DataTempate/DataTemplateSelector, Binded to ViewModelB --> Binded to ModelB
This IS possible.
Now if you model your view-models and models like this (pseudo code):
public PhoneBookViewModel
{
public PhoneBookViewModel()
{
_parties = new ObservalbeCollection<PartyViewModel>();
}
private PhoneBook _dataContext;
// This is the property the VM uses to access the model
public PhoneBook DataContext
{
get { return _dataContext; }
set
{
if (_dataContext != null)
{
_dataContext.Parties.CollectionChanged -= OnModelPartiesChanged;
}
_dataContext = value;
if (_dataContext != null)
{
_dataContext.Parties.CollectionChanged += OnModelPartiesChanged;
}
}
}
private ObservableCollection<PartyViewModel> _parties;
// This is the property the view uses to access the collection of VM parties
public ObservableCollection<PartyViewModel> PartiesViewModels { get { return _parties; } }
private void OnModelPartiesChanged(...)
{
// Add/remove VMs to/from PartiesViewModels here
}
}
// Model
public PhoneBook
{
public PhoneBook()
{
_parties = new ObservalbeCollection<Party>();
}
private ObservableCollection<Party> _parties;
// This is the property the VM uses to access the model's parties
public ObservableCollection<Party> Parties { get { return _parties; } }
}
public PersonViewModel : PartyViewModel
{
new Person DataContext { get; set; }
}
public PartyViewModel
{
public Party DataContext { get; set; }
}
then you will get correct type of VMs for each model item,
view will be binded to VM items, not model items.
View's datatemplates:
<DataTemplate x:Target={x:Type myVmNamespace:PersonViewModel}">
<PersonView/>
</DataTemplate>
<DataTemplate x:Target={x:Type myVmNamespace:GroupViewModel}">
<GroupView/>
</DataTemplate>
View's itemscontrol:
<!-- Bind to Parties property of PhoneBookVM -->
<!-- Uses datatemplates for items -->
<ListView ItemsSource={Binding Parties}"/>
Configure a data template for your data-types to render the xaml.
If you're using Prism and MVVM then you are binding a Command to your ItemsControl with all the Parties.
This command would be of type DelegateCommand<Party>. Inside the Delegate that the command is executing, which would look like:
private void PartyNavigate(Party party)
Just check if the party is any of the sub types and call a RequestNavigate on the region of your RegionManager to the specific View.
It will then become a problem how you pass the actual context, you can either look at the MVVM RI that comes with Prism, which has a very good aproach to that in the form of the StateHandler or you can build your own centralized DataManager where you keep state of those things, along with caching stuff you are getting from WebServices, etc. After 2 years of building smart clients with WPF and WCF I can tell you that you will eventually need to build your own DataManager, which won't be that much of a deal if you're already using EntityFramework and you generate most of it from the EDM.
Your collection for your ItemsControl ItemsSource is filled by PhoneBookViewModel. So the only thing left is to tell WPF how each item of this collection should be rendered. And this can easy achieved by creating a DataTemplate.
<DataTemplate DataType="{x:Type PersonViewModel}">
<MyPersonView/>
</DataTemplate>

2 (or more) ComboBoxes dependent on each other

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

Binding and appendText result to a TextBox

Total beginner in WPF so bear with me.
I building a proof of concept application before I go ahead with it. I have decided to try building it using WPF first.
This is the scenario:
I have:
a View=CustomerView (userControl with a button "Buy Products")
a txtbox (append all the actions that occurs)
ViewModel=CustomerViewModel (talks to the service etc.. and gets results
Model =Customer (simple class with properties with InotifyPropertyChanged implemented
Given that I have a collection of products and for each product that I buy I need to APPEND TEXT TO the txtBox in the userControl printing all the actions that occurs, the textBox should look like
Start buying .....
Product 1 Sold
Product 2 Sold
Fineshed....
My problem is that despite I notify successfully after each item is sold I cannot seem to see how I can bind or make "Product 1 Sold,Product 2 appear in the textbox.
In windows forms I would have a userControl with a property called ProductStatus and whenever notified I would appendText to the textBox.
"I put beginInvoke as I was getting threadCross operation" when coming from a service. That is another problem that I will have to investigate
private ProductStatus _productStatus;
public ProductStatus Status
{
get { return _productStatus; }
set
{
_printStatus = value;
BeginInvoke((MethodInvoker)(() => txtProductResult.AppendText(string.Format("Product Sold {0}", Environment.NewLine))));
}
}
How do I append text to myTextBox for each item I sell?
=================Added current code===============================
void LogChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach(object item in e.NewItems)
{
txtProduct.AppendText(item.ToString());
}
}
<TextBox Margin="12,41,12,59" Name="txtPrintResult" />
<TextBox Text="{Binding TicketPrintLog, Mode=OneWay}" Margin="12,41,12,12" />
ProductModel
=============
public string ProductLog{ get; set; }
ProductViewModel
==================
public string ProductLog
{
get { return _ProductModel.ProductLog; }
}
internal void AppendToProductLogText(string text)
{
_ProductModel.ProductLog += text + Environment.NewLine;
OnPropertyChanged("ProductLog");
}
void ProductSoldNotificationReceived(object sender, notificationEventArgs e)
{
//THIS FIRES WHENEVER I PRODUCT IS SOLD.
AppendToProductLogText(e.Message);
}
ProductView (userControl) XAML
================================
<TextBox Margin="12,41,12,59" Name="txtResult" Text="{Binding ProductLog, Mode=OneWay}" />
IN CODE BEHIND I DO
public void Action1()
{
txtResult.AppendText(_productViewModel.ProductLog);
}
public void SellProductAction()
{
txtResult.AppendText(_productViewModel.ProductLog);
}
The problem i have is that i still have todo this
txtResult.AppendText(_productViewModel.ProductLog);
which defeats the point on databinding also when executing SellproductAction I only get notified about the last item, it doesn't append Product1 sold, Product 2 sold etc…
Any ideas?
First off, you can still do this the Windows Forms way, by appending the new text to the existing text:
txtProductResult = txtProductResult + Environment.NewLine + "Product sold";
(If you do this from a background thread you will need to use txtProductResult.Dispatcher.BeginInvoke instead of txtProductResult.BeginInvoke.)
However, the idiomatic way to do this in WPF is with data binding. So you would create a ProductSalesLog property on your view model, and bind the TextBox to that property. Then, whenever you update the ProductSalesLog property in the view model, the TextBox will update automatically. Note that your view model must implement INotifyPropertyChanged and must raise the PropertyChanged event for the ProductSalesLog property. Thus:
public class CustomerViewModel : INotifyPropertyChanged
{
private string _productSalesLog = String.Empty;
public string ProductSalesLog
{
get { return _productSalesLog; }
}
internal void AppendSalesLogText(string text)
{
_productSalesLog += text + Environment.NewLine;
OnPropertyChanged("ProductSalesLog");
}
}
And in your view:
<TextBox Text="{Binding ProductSalesLog, Mode=OneWay}" />
As WPF is a data driven framework, you should have your view databinding to properties on your view model and model. Any changes in the viewmodel or model can then be passed to the view through the INotifyPropertyChanged interface (by raising a propertyChanged event).
This means for most purposes you should not need to access properties on your user control. This should be possible by having a product status on your viewmodel and your text box should bind to this property. You will of course have to make sure when the setter for product status is called that the PropertyChanged event is raised to notify the view to update

Sending data from view to viewmodel with command binding

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.

Resources