I have recently started investigating the MVVM pattern with WPF for an upcoming project. I started with Josh Smith's MSDN article. I have a question (well many, but let's start with one):
I have an IndividualViewModel which exposes the properties of the model. I need two views "Add Individual" and "Edit Individual" which are very similar as you can imagine. What I have done currently is to have 2 subclasses AddIndividualViewModel and EditIndividualViewModel which expose the Add and Edit commands respectively. I also have 2 similary named views that bind to these.
Now this method works and these classes are fairly small anyway, but I'm wondering if it is possible for me to have just the one view model, which exposes both commands. I would still have 2 views which would bind to this same view model, exposing the appropriate command as a button. I'm not quite sure how to do this. In the main window resources I have something like:
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
With this method of binding you can only have a one-to-one binding, i.e. the same view is always shown for a given view model. Is there a way to automatically switch the view depending on a property on the view model (e.g. IndividualViewModel.Mode). Is there a different approach I should be considering?
Note that the main window has a collection of view models and shows each in tab.
Thank you!
So you need 2 different views based on a property value. One thing to consider is to refactor your presentation code, so instead of the values of a property you could have real subclasses. Then you can use 2 different DataTemplate for each class.
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:EditIndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
If you think that is an overkill, you could use a trigger and wrap your specific views into a ContentPresenter.
<DataTemplate x:Key="AddIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate x:Key="EditIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:IndividualViewModel}">
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource AddIndividualTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="{x:Static ViewModels:IndividualMode.Edit}">
<Setter Property="ContentTemplate" Value="{StaticResource EditIndividualTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
Thanks for pointing me in the right direction! I am still new with WPF too and learning about all the different possibilities including binding methods. Anyway for anyone interested, here is the solution I arrived at for this particular case:
I decided I wanted to keep the view models separated in two subclasses AddIndividualViewModel and EditIndividualViewModel which only expose commands, rather than trying to manage state in the one class. However I wanted one view so that I'm not duplicating the XAML. I ended up using two DataTemplates and DataTemplateSelector to switch out the action buttons depending on the view model:
<DataTemplate x:Key="addTemplate">
<Button Command="{Binding Path=AddCommand}">Add</Button>
</DataTemplate>
<DataTemplate x:Key="editTemplate">
<Button Command="{Binding Path=UpdateCommand}">Update</Button>
</DataTemplate>
<TemplateSelectors:AddEditTemplateSelector
AddTemplate="{StaticResource addTemplate}"
EditTemplate="{StaticResource editTemplate}"
x:Key="addEditTemplateSelector" />
and a content presenter at the bottom of the form:
<ContentPresenter Content="{Binding}"
ContentTemplateSelector="{StaticResource addEditTemplateSelector}" />
Here is the code for the template selector:
class AddEditTemplateSelector : DataTemplateSelector
{
public DataTemplate AddTemplate { get; set; }
public DataTemplate EditTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is AddIndividualViewModel)
{
return AddTemplate;
}
else if (item is EditIndividualViewModel)
{
return EditTemplate;
}
return null;
}
}
This may or may not be how implement the final thing (given the requirements) but it's good to see I have this sort of option available.
There's no reason why you shouldn't be able to achieve that. One way of doing this is to provide some flag in your view model stating whether you're in add mode or in edit mode, and styling your view based on that flag using simple bindings, triggers or template selectors.
For reference you may look at Sacha Barber's DataWrapper class that's part of his Cinch framework (not directly applicable to your case, but it's a good starting point) which wraps data fields in the view model in such a way to support a flag to toggle between read only (view record mode), and read-write (edit record mode). You could apply a similar approach to make the distinction between add and edit.
Basically, instead of having simple properties in your view model, instantiate a data wrapper class which includes a Value property, and a IsAdding property. In your view, you can use bindings, triggers or template selectors to modify templates based on that property.
For this task you do not need any DataTemplateSelector at all.
Derive both EditIndividualVM and AddINdividualVM from IndividualVM.
The Edit- and AddCommands route to a setter property in the IndividualVM.
The setter VM = new AddIndividualVM or VM = new EditIndividualVM depending on which button is pressed.
In xaml you bind in the contentgrid to your VM property like this:
Related
I have an app written in WPF (MVVM), which based on some conditions, will create instances of different UserControls, These UserControls are completely independent, used to display certain information. They have some custom logic inside, like timers and so on, so I can't use Templates.
Now I face the problem that I want to create a list of UserControls in the ViewModel, and bind the host UI to it. The problem is that I don't know how to bind and what to bind. In a non MVVM project, you would simply get the layout where you want to put your controls, and add them there as children. In MVVM app, I don't know how to do this. I imagine having a WrapPanel with ItemsSource, that will add all the controls and resize itself as needed, based on the UserControls.
Can someone suggest a solution?
EDIT:
My ViewModel exposes an ObservableCollection of IMyDriver right now. So that's what I thought, to break a little bit MVVM to get what I describe next:
Now, Each IMyDriver can be a different type of driver, and can implement different other interfaces. I need the UI to create specific UserControls that know how to get maximum from these Drivers, based on their capabilities. In short, the UserControls connect to the device through the driver for polling data. And each UserControl does it in a specific way.
You can do it quite simply and easily by declaring specific data type classes for the data in each UserControl and define DataTemplates that expose your UserControls in the App.xaml file:
<DataTemplate DataType="{x:Type YourViewModelsPrefix:YourViewModel">
<YourViewsPrefix:YourView />
</DataTemplate>
<DataTemplate DataType="{x:Type YourViewModelsPrefix:YourOtherViewModel">
<YourViewsPrefix:YourOtherView />
</DataTemplate>
<DataTemplate DataType="{x:Type YourViewModelsPrefix:AnotherViewModel">
<YourViewsPrefix:AnotherView />
</DataTemplate>
Now whenever the Framework comes across an instance of these view model classes, it will render the associated view/UserControl. You can display them by having a property of the type of your view model using a ContentControl like this:
<ContentControl Content="{Binding YourViewModelProperty}" />
...
public YourBaseViewModelClass YourViewModelProperty { get; set; }
Make sure that all of your view models extend this class:
public YourViewModel : YourBaseViewModelClass { }
...
public AnotherViewModel : YourBaseViewModelClass { }
Then you can swap each view model (and display each related view) like this:
YourViewModelProperty = new AnotherViewModel();
Based on what Will commented, and what Sheridan answered, I have found the solution to my problem.
So:
I don't break MVVM by leaving ViewModel types intact.
I create DataTemplates in my Window's Resources tag, and in each data template, I assign the DataTemplate to be my UserControl defined in another assembly (UICommons)
<DataTemplate x:Key="IMultiChannelMeasurementDCDataTemplate">
<uicommon:MeasurementMax8ChannelMonitoringUserControl/>
</DataTemplate>
I create a Template Selector in my application assembly, and based on the interfaces the DataTypes implement, I return the right DataTemplate, that I assign in the same Window's Resources tag
<!-- DataTemplate Selector -->
<local:DriverComponentDataTemplateSelector x:Key="templateSelector"
DefaultDCDataTemplate="{StaticResource DefaultDCDataTemplate}"
IIhcDCDataTemplate="{StaticResource IIhcDCDataTemplate}"
IMultiChannelMeasurementDCDataTemplate="{StaticResource IMultiChannelMeasurementDCDataTemplate}"
IProgrammablePowerSourceDCDataTemplate="{StaticResource IProgrammablePowerSourceDCDataTemplate}"
IEnvDCDataTemplate="{StaticResource IEnvDCDataTemplate}"/>
I create an ItemsControl in the Window, with the following XAML code, that binds itself to my ObservableCollection of items
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemTemplateSelector="{StaticResource templateSelector}" ItemsSource="{Binding DriverComponentsInfo}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" x:Name="ucWrapPanel">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
I enjoy dynamically created UserControls based on different Drivers!
P.S. I upvoted Will's comment and Sheridan's answer, because without these, I wouldn't be able to find the solution. Thx!
They have some custom logic inside, like timers and so on, so I can't use Templates.
This does not follow. I think you may have a misconception about the capabilities of WPF.
Also, as you want to use MVVM: Binding to a list of UserControls is breaking the pattern. View-models should only ever reference other view-models (and models); they do not know anything about the UI. Bind to a collection of view-models which have associated UserControls as their views (consider using implicit DataTemplates). To bind a WrapPanel you use an ItemsControl and set its ItemsPanel accordingly.
I have a WPF/MVVM app with a ListBox which displays data through a DataTemplate. I managed to change the selected item in the ListBox when pressing a button so the CommandParameter is linked to the ListBox's SelectedItem, but I cannot get the buttons to be enabled/disabled correctly in the same way. For example, if I have 2 items and the button should be enabled in one and disabled in the other, when I select an element BOTH buttons have the same state, and they BOTH change state when I select another item.
I am using a RelayCommand as used in many MVVM Frameworks.
Here is my XAML (removed "not interesting" parts):
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<Grid>
<Button Content="Something" Name="EnabledDisabledButton" Click="Button_Click"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SomeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItem}"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
</Style>
</UserControl.Resources>
<ListBox x:Name="myListBox" ItemsSource="{Binding ElementList}"
IsSynchronizedWithCurrentItem="True" ItemContainerStyle="{StaticResource ContainerStyle}"/>
I tried to pass the SelectedItem as a parameter to the RelayCommand's CanExecute method, but the result was the same as before.
Is there a way to pass the actual ListBoxItem in which the button "lives in" as a parameter to the command, so each one will be processed separately by the CanExecute method? Would it work if I got this? (right now I am handling the Click event to select the correct item in the list before executing the command).
In my CanExecute method I am evaluating some property of the SelectedItem in order to enable/disable the corresponding button. An alternative would be to evaluate this property for all elements, but I cannot think of a way to do it inside the ViewModel, and then communicate to the view the result (if it is even possible while using a DataTemplate for the items).
Thanks for your input, regards!
Converting My comment into an answer:
Why not just CommandParameter="{Binding}"?
You mention "MVVM" in the question, but it seems you use the MVVM way to your full advantage.
I would not have a Button_Click event in the style at all. That is because it is in fact a style, which per definition could be changed to another style which does not have the same event, which again will make the application stop working as wanted if you choose to have a style-based app in the future.
A rule I use is that a style is a style. A style has to do with the UI and "looks" of the app.
Functionality should be separate from the UI. The programmer can define the Command, and the designer can decide how the user will use that in the best way.
That's exactly where the code separation from the MVVM pattern cames into grip.
To separate the "looks" and user behavior and the app's logic.
Like...it should not matter to the model if a command fires from a button, a menu, a datacontext or a key stroke.
If this particular problem was handled to ME, I would solve it by having a HOLDER-class.
This is a class (DependencyObject which implements INotifyPropertyChanged) that holds a ICommand property as well as the "row" that will be displayed in the various rows in the ListBox.
The ICommand property will be bound to the Button, having the row (class) itself as CommandParameter to the call.
Then the actual row would be used in the ItemTemplate on the ListBox, with Bindings to different elements (proprty with or withouy Converters) to make whatever desired display available.
I hope I explained good enough...
Feel free to ask more if you want more details to my solution alternative.
I want to double-click to copy an item from a "shopping list" to a "shopping cart" list box. Right now, my model just has ObservableCollection of strings for each list, but eventually the objects will get more complex.
The ViewModel is mapped to the view using a DataTemplate. Right now, I just have a "Session" property on my ViewModel that is exposing my Session object in my Model that contains both ObservableCollections.
I tried this...
<ListBox Name="listBoxShopList" ItemsSource="{Binding Path=Session.Products}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItemMouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ListBox Name="listBoxCart" ItemsSource="{Binding Path=Session.CartItems, UpdateSourceTrigger=PropertyChanged}"/>
From code-behind I do get the event and I can get the SelectedItem. But being new to MVVM, I cannot figure out how to add the item to the "Cart" collection. It seems like I should be able to access the ViewModel Session.CartItems directly since the View can. Is a parameterized command the way to go? If so, any recommended articles?
In your event handler code-behind you could get the ViewModel like this:
var viewModel = DataContext as <YourViewModelType>;
And then transfer the selected item to the cart.
The preferred way to do it would be using a command, like DelegateCommand.
Well, you get your handler (a part of view code) called on double click. Good so far.
Now, you need to inform the VM that double-click happened (or better put some semantics here: selection changed, shopping cart accepted, etc.) by invoking a command (preferred way), or communicating with the VM through DataContext (easy way). Your VM can update the ObservableCollection as appropriate, and the view will get the changes through the usual binding.
imagine the following simple Models (example for simplicity reasons; in fact, we have MVVM here but it doesn't matter):
public class User {
public string Username { get; set; }
}
public class StackOverflowUser : User {
public int Reputation { get; set; }
}
Now we have a Silverlight UserControl which contains the following Controls (again, this is just an example, stripped down to the core):
<Grid>
<TextBlock Text="Username:" />
<TextBlock Text="{Binding Path=Username}" />
<TextBlock Text="Reputation:" />
<TextBlock Text="{Binding Path=Reputation}" />
</Grid>
Now I'd like this UserControl to be compatible with both Models, User and StackOverflowUser. I might set the UserControl's DataContext to either a User or StackOverflowUser Type:
this.DataContext = new User { Username = "john.doe" };
If set to StackOverflowUser, everything works fine. If set to User, I'm getting a "BindingExpression Path error", because the Property Reputation is missing in the User Model. Which I understand completely.
Is there any way to 1) avoid this
exception and 2) control the
visibility of the controls, collapse
when bound property is not available?
Of course, we prefer an elegant solution, where the problem is solved by tuning the Binding Expression and/or using Converters etc. and avoid tons of code behind if possible.
Thanks in advance for your help and suggestions,
best regards,
Thomas
Unfortunately Silverlight is limited in its polymorphic behavior regarding DataTemplates, I can only think of a workaround:
Give the User class the property Reputation too, but make it meaningless, for example -1. Then apply a style to the reputation TextBlocks:
<Page.Resources>
<Style Key="Reputation">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Reputation} Value="-1">
<Setter Property="Visibility" Value="Invisible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
...
<TextBlock Text="Reputation:" Style="{StaticResource Reputation}">
<TextBlock Text="{Binding Path=Reputation}" Style="{StaticResource Reputation}">
You could also try (I can not test this):
giving the User class a new property that identifies its type,
make a second Style for the second TextBlock
bind its DataTrigger to the type identifying property and move the {Binding Path=Reputation} declaration into a Setter:
<Style Key="ReputationContent">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type} Value="StackOverflow">
<Setter Property="Visibility" Value="Invisible" />
<Setter Property="Text" Value="{Binding Path=Reputation}" />
</DataTrigger>
</Style.Triggers>
</Style>
But you see, there is no elegant way, it's a shame that DataTemplate do not have a DataType property in Silverlight.
You mentioned you're using MVVM. This is the value of your viewmodel - to shape model data in preparation for the view. The viewmodel could have accessible properties for both username and reputation (and maybe even another bool for binding the visibility). The viewmodel would include all logic on how to fill those properties from either model (User or StackOverflowUser). The view would have no knowledge of a User or StackOverflowUser object, just the viewmodel.
I finally got my problem solved. A co-worker finally implemented a solution including a workaround for WPFs DataTemplates DataType attribute (or generally, a DataTemplateSelector). It's not very pretty (i guess, no workaround is) but it works. Unfortunately, i cannot post any code snippets because its closed-source. But i found some links afterwards, providing a pretty similar solution, like this one: Silverlight: a port of the DataTemplateSelector. If you have a similar problem, this will help you as well. Here or there are more thoughts on this subject.
The actual solution is following Ozan's hints. Unfortunately, his solution is not working so I don't want to mark his comment as the accepted answer but I give at least an upvote.
Thanks!
best regards,
Thomas
I know this has already been answered, but I still think its worth this post. Using reflection you can have a property in your ViewModel that will easily handle Dto objects which only sometimes have the property. Reflection can be expensive though, so weigh that with your decision.
public int? Reputation
{
get
{
var prop = Dto.GetType().GetProperty("Reputation");
return (prop != null)? (int)prop.GetValue(Dto, null) : null;
}
set
{
var prop = Dto.GetType().GetProperty("Reputation");
if(prop !=null) prop.SetValue(Dto,value, null);
}
}
In WinForms it was relatively easy to swap out Panels at runtime for other panels. In WPF this seems to be rather more complex (especially from XAML).
Can anyone provide clear guidance on the 'best practice' way of swapping gui elements at runtime (think pages in a wizard type situation).
Many thanks.
This can be approached in XAML using datatemplates and/or triggers. For example, if each page in your wizard were represented in an underlying model as a separate class or object, you could use one of the following two options... Both use a ContentControl, which is the perfect control for when the content will vary greatly between different views of the same data.
Please note that the bindings are intended as pseudocode examples, just to convey intent!
DataTemplate-based, using different classes for each page:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type WizardPageOne}">
<!-- page 1 layout here -->
</DataTemplate>
<DataTemplate DataType="{x:Type WizardPageTwo}">
<!-- page 2 layout here -->
</DataTemplate>
<!-- ... etc -->
</Grid.Resources>
<ContentControl Content="{Binding CurrentPageModel, Source=Wizardmodel}" />
</Grid>
Or Trigger based, using a property that indicates the current page:
<ContentControl Content="{Binding WizardModel}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 1 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="2">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 2 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- .... etc -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Both options will only load the control for each page as it's required, so you don't have all of the controls "loaded but hidden" in the window.
The underlying concepts of WinFomrs and WPF is different. In WPF it is not advisable to play around with UIElements(Controls) directly. Make use of DataBinding/DataContexts and just operate on the data and then the UI will function accordingly. This concept is all about WPF MVVM pattern. You can look in to some MVVM samples and try it before doing more complex WPF projects.
A simple example, Suppose you need to dynamically disply a number of items in a ListBox, The typical winform way to do this is to create Items and add directly to the ListBox. But in WPF you create an ObservableCollection<Customer> and bind that to the ListBox.ItemsSource. then define a DataTemplate for Customer Data Type, this ensure the WPF system to understand how a Collection of Customers being displayed in the application. So when you add a new customer instance to the collection, magically your ListBox will get updated with one more item. Seems pretty straight forward and a very loosely coupled way of Data and View right?.
Best wishes on your WPF learning. -
http://www.bing.com/search?q=WPF+MVVM
So the high level clue to your question is, make the View appropriately for the Data and when Data/Property Change happens, WPF will take care of changing the Panels/Controls. So it is really simple than WinForms way when you approach from the Data and View perceptive.
A couple options come to mind. If you create your components as UserControls, and make use of Data Binding, then you should be able to do what you need with minimal fuss.
Option one is to load each component into your parent container (grid, canvas, whatever) with Visibility="Collapsed", and then show and hide them as needed. This has the advantage that you can do this declaratively in XAML.
The other option is to load the components as you need them, so in the event handler of a button, or some other UI element. In this case you would probably want to remove the current displaying item from the Children collection of your host component, and then instantiate your next control, set the DataContext (this is why binding is important), and add it to the Children collection.
(disclaimer: this is based on my experience doing basically what you are asking in Silverlight 3.0, so there may be some WPF quirks I am unaware of).
The MVVM suggestions here are all good. But if you're designing a page-oriented UI that needs to be navigable, you can use Structured Navigation, too.
I got no idea if this is considered good practice, but what we did on one of our project is quite simple. We defined panels that were all on top of each other and would simply set the visibility to either hidden or visible when it was needed.