Inject views into ItemsControl depending on object type - wpf

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>

Related

How should I populate the ViewModel in WPF?

I'm new to WPF and I'm writing a simple test app to familiarize myself with it. My test app will detect all joysticks I have attached to my computer and display information about it. So far, I have this ViewModel:
public class JoystickViewModel
{
public ObservableCollection<Joystick> Joysticks { get; set; }
public JoystickViewModel()
{
GetAttachedJoysticks();
}
private void GetAttachedJoysticks()
{
// populate Joysticks collection by using SlimDX
}
}
And this is my codebehind for my MainWindow.xaml:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new JoystickViewModel();
}
}
And my XAML for MainWindow:
<Window ...>
<Grid>
<ComboBox ItemsSource="{Binding Joysticks}"
DisplayMemberPath="Information.ProductName"/>
</Grid>
</Window>
I followed a tutorial that also populated the ViewModel in its constructor.
My question is, how should I populate the ViewModel? It seems sort of weird to me that I'm population the collection in the ViewModel constructor. Should this logic be in MainWindow's codebehind instead? Or somewhere else altogether? The end goal is to not only have this collection populated, but also updated periodically to reflect the current state (user plugged in new joystick, unplugged existing one, etc...).
The MainWindow code behind is definitively not the place where "business" logic should occur, as the View should be kept as simple as possible.
Keep your fetch/update logic inside of your viewmodel, this way you can test it easily and independently.
From a learning perspective, it's important to keep concerns separated :
the View is bound to the ViewModel, and has no intelligence
the ViewModel has knowledge on how to get the Model
the Model represents the data
In your case, the VM knowledge is at the moment a call inside it's constructor. Later you can change this to call some IJoystickDataService interface, and wire everything using a MVVM framework.
I would have your JoySticks observable collection property (and the code that populates it) in a Model class. The viewmodel simply exposes this same property to the view for binding. The vm should be as thin as possible - ideally just exposing properties that are in the model for binding and not doing any kind of 'business' logic (i.e. populating joystick info as in your case).

How to reuse entity framework datasource for multiple views WPF EF MVVM

Hi I am designing an application using WPF4, EF and MVVM. I want to be able to create reusable UserControls that I can use in multiple windows in the application, and have them draw data from the same source.
Lets say I have a GraphView component and a TableView component that can appear on the same page or in different places in the application, and I want them to both reflect the same collection of filtered EF entities. MVVM common practice seems to require that each view has its own viewmodel. But should I be be using a joint viewmodel and bind both to it, so if you change the data or filter, both would update simultaneously? If not how should I handle this?
Thanks for any advice!
One approach could be to have two ViewModels, one for each of your Views/UserControls, and then nest them into some top or higher level ViewModel. If, for example, both Views reside in a MainWindow View, it could look like this:
public class MainWindowViewModel
{
public MainWindowViewModel(IRepository repository)
{
SharedUserControlData sharedData = new SharedUserControlData()
{
MyCollection = new ObservableCollection<MyEntity>(
repository.GetMyEntities()),
// instantiate other shared data properties
}
UserControl1ViewModel = new UserControl1ViewModel(sharedData);
UserControl2ViewModel = new UserControl2ViewModel(sharedData);
}
public UserControl1ViewModel UserControl1ViewModel { get; private set; }
public UserControl2ViewModel UserControl2ViewModel { get; private set; }
// more stuff...
}
You have a SharedUserControlData class which contains properties both views can bind to:
public class SharedUserControlData : INotifyPropertyChanged
{
public ObservableCollection<MyEntity> MyCollection { get; set; }
// other properties
// INotifyPropertyChanged implementation
}
And the ViewModels of the UserControls get those data injected:
public class UserControl1ViewModel
{
public UserControl1ViewModel(SharedUserControlData data)
{
SharedUserControlData = data;
}
public SharedUserControlData SharedUserControlData { get; private set; }
// more stuff
}
// and the same for UserControl2ViewModel
Your UserControl Views are bound to the ViewModels by a DataTemplate:
<DataTemplate DataType="{x:Type vm:UserControl1ViewModel}" >
<v:UserControl1View />
</DataTemplate>
// and the same for UserControl2ViewModel
And some controls inside of the UserControls are bound then to SharedUserControlData.MyCollection and other properties of the UserControlViewModels. The DataContext of the MainWindow is the MainWindowViewModel:
IRepository repository = new MyRepository(); // or use Dependency Injection
MainWindow window = new MainWindow();
MainWindowViewModel viewModel = new MainWindowViewModel(repository);
window.DataContext = viewModel;
In the XAML of your MainWindow we bind the UserControls to the nested ViewModels of the MainWindow's DataContext (which is the MainWindowViewModel):
<StackPanel>
<v:UserControl1View DataContext="{Binding UserControl1ViewModel}" />
<v:UserControl2View DataContext="{Binding UserControl2ViewModel}" />
</StackPanel>
This way both UserControls would have different ViewModels but both share the same SharedData instance which comes from the higher level ViewModel containing both UserControl's ViewModels. The Repository then has access to the EF data context. (Having repositories here is only an example, you could also inject instances of Service classes or something.)
Your EF classes, near as I've been able to tell after only four days using EF, reside at the project level. My first instinct would be to implement a singleton containing references to the entities you want to hold common across your viewmodels. That will create a class dependency on your singleton, of course.
This actually sounds like a design problem addressed by Unity, MEF, or something else that will do dependency injection. You'd have your EF classes in a module of one of those frameworks and use their protocols to coordinate between EF and your VM's. Then a change in your filter or your data in EF would also trigger a message your VM's could register to receive, in order to trigger UI changes or VM state changes or whatever.
I agree wholeheartedly with the one ViewModel per View approach. For shared data you can either pass references around (tedious and error prone), you can use DI (depending on your comfort level but doesn't play well with design time data), or you can create static properties in your App.xaml.cs which are then shared and accessible throughout the application. In the long run, DI will probably get the most support from other folks.
You might have a look at the BookLibrary sample application of the WPF Application Framework (WAF). It contains two different Views (BookListView [Master], BookView [Detail]) for the same data source which is provided by the Entity Framework.

MVVM: Giving every modular part it's own XAML class

I was thinking about doing this instead defining lot's of DataTemplates. This would mean that if I had a collection of things the ItemsControl itself would have a XAML class and the objects would have one too.
This is something that already happens when the objects are proper ViewModels containing models and logic but if it's just a Command for example. A dynamic group of commands perhaps.
Pros: I could use the designer to help me define the look of the object as I don't have blend and it would be easier to find and change those parts if needed.
Cons: More XAML classes.
Would you talk me into this or out of this.
EXAMPLE
I have buttons all around the app so I define a ButtonViewModel which has a display name and a ICommand Property. I would also define a DataTemplate or UserControl for this object which would basically be a button with Command binding and text/content binding to the display name. I could also define it's look and such.
Then in ViewModels that should include buttons I would add these buttons as part of the class and bind to them inside the view.
public class ButtonViewModel : ViewModelBase
{
private string _displayName;
public string DisplayName
{
get
{
return _displayName;
}
set
{
_displayName = value;
RaisePropertyChanged("DisplayName");
}
}
private ICommand _command;
public ICommand command
{
get
{
return _command;
}
protected set
{
_command = value;
RaisePropertyChanged("Command");
}
}
public ButtonViewModel(ICommand command, string displayName)
{
Command = command;
DisplayName = displayName;
}
}
ViewModel using the ButtonViewModel
public class SomeViewModel : ViewModelBase
{
//some functionality
//It could be done as a collection or just seperate ButtonViewModel properties
public ObservableCollection<ButtonViewModel> Buttons { get; set; }
//Somewhere where it makes sense, here in the constructer for the heck of it
public SomeViewModel()
{
Buttons.Add(new ButtonViewModel(new RelayCommand(Save, canSave), "Save"));
Buttons.Add(new ButtonViewModel(new RelayCommand(Edit, canEdit), "Edit"));
Buttons.Add(new ButtonViewModel(new RelayCommand(New, canAddNew), "New"));
}
}
The buttons view:
<UserControl x:Class="WpfApplication1.ButtonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="60" Width="90">
<Button Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}">
<!-- Some really cool design for your button -->
</Button>
</UserControl>
You could also define a specific ItemsControl to hold a collection of buttons, even going so far as to define a ViewModel for said itemscontrol.
I once learned that if you can encapsulate some item in a class you should. Is this just crazy talk?
I'm not quite sure what you're asking, but it sounds as if you are taking a view first approach, which can get very complex in everything but the simplest of apps. Have you considered using an MVVM framework such as Caliburn.Micro?
Using a view model first approach, you can instantiate your view model, and then use Caliburn.Micro to locate your view (via convention), and automatically bind the two up.
Caliburn.Micro will also do view composition, so for example, if you have a collection of view models on your parent view model, and you expose that collection from a property with the same name as a ListBox on your view, then Caliburn.Micro will automatically use the corresponding view for each item in the collection, and bind up each items view model with the view.
You can also use different views over the same view model, and Actions are used to invoke verbs on your view models from view controls, rather than commanding, which allows for much richer imagining of UIs.

Silverlight: how to bind List<T> to data grid

MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.

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

Resources