UWP app: insert / remove user control MVVM way - c#

I'm learning UWP at the moment in an attempt to port an old Win32 to the new platform. I'm using Template10 and everything runs fine so far, except I'm bit confused on how to implement the problem below.
Problem: In a page, I have to constantly remove and insert user controls depending on a view model property. The user controls are fairly complex and they all look and behave differently. Imagine a wizard with back and next buttons. On every click I have to remove the old content and insert a new one, with completely different view model.
Question: What would be the recommended way of implementing this in a MVVM way?
At the moment, my only idea is to send a message from the page's view model and subscribe for the message in page's code behind where I can create the required component and insert it dynamically in the page (after removing the old one).
In MyPageViewModel:
public IComponentViewModel CurrentComponent {get; set;}
...
public DelegateCommand NextItemCommand = new DelegateCommand(() =>
{
var evt = App.EventAggregator.GetEvent<ItemChangedMessage>();
evt.Publish(CurrentComponent);
});
In MyPage.xaml.cs code behind
public MyPage()
{
InitializeComponent();
var evt = App.EventAggregator.GetEvent<ItemChangedMessage>();
evt.Subscribe(OnItemChanged);
}
private void OnItemChanged(IComponentViewModel viewModel)
{
switch (viewModel.Type)
{
case 1:
// create the new user control and insert it in the container
var component = new TypeOneComponent();
component.DataContext = (TypeOneCompoentViewModel)viewModel;
// ...
case 2:
...
}
}
Not sure this is the best approach tho.

I've been thinking about a Wizard approach lately myself. It seems to me that a FlipView with re-templated left/right buttons is the easiest approach. My WizardViewModel would have several children view-models; something like Page1ViewModel, Page2ViewModel, and so on. I strongly feel that each page view-model would have a dedicated UserControl so the UI can be unique but not dynamic - I think it makes sense to design against dynamic UI, while embracing an adaptive UI - which is a different concept altogether.
The pseudo code might look like this:
public interface IWizardPage { }
public class Page1ViewModel : ViewModelBase, IWizardPage { }
public class Page2ViewModel : ViewModelBase, IWizardPage { }
public class Page3ViewModel : ViewModelBase, IWizardPage { }
public class MainPageViewModel : ViewModelBase
{
public IWizardPage CurrentPage { get; set; }
public IWizardPage Page1ViewModel { get; set; }
public IWizardPage Page2ViewModel { get; set; }
public IWizardPage Page3ViewModel { get; set; }
}
And this:
<FlipView Template="{StaticResource WizardFlipView}"
SelectedItem="{Binding CurrentPage, Mode=TwoWay}">
<Page1UserControl DataContext="{Binding Page1ViewModel}" />
<Page2UserControl DataContext="{Binding Page2ViewModel}" />
<Page3UserControl DataContext="{Binding Page3ViewModel}" />
</FlipView>
This is just a recommendation. But to answer your question, this would be very amenable to the MVVM pattern. I also think this would allow for you to be very flexible without getting so dynamic that maintenance is affordably time consuming. There are lots of ways to do wizards. I think this would be a fine solution, good with the MVVM pattern and fine with Template 10.
Best of luck.

I typically use an ItemsControl. This allows you to have a generic item template and a item specific template if you want and you can add / remove items at will by binding to the ItemsSource.
In your example of a wizard, you might make the main wizard container an ItemsControl that only shows one item at a time and a page would be an "item". The distinction with MVVM is that you don't add child controls, you add data and then specify a template to render it. So your items are going to be simple databound poco objects.
For your actual example, I guess you can add child controls to the ItemsControl and they would render automatically without even using a template since a ContentPresenter knows how to render controls. I would still use data only classes though since one of the tenants of MVVM is to separate the data from the UI. So your child item would be a model and your item specific template would be the UI layout bound to the item data.

Related

wpf mvvmlight passing data to viewmodel

I have the following views and viewModels View: Staff, VM: StaffViewModel and View: Notes, VM: NotesViewModel.
My StaffViewModel has a SelectedStaffMember property of type SelectedEmployee.
The Staff view has a button that launches another view (Notes).
When the user enters a note I need to save it against the SelectedEmployee, which means the NotesViewModel needs to know the currently selected employee ID.
Right now Im doing this via my ViewModelLocator but this seems wrong, what is the corret way to pass data to a VM???
I'm using MVVM Light.
Relevant code - StaffViewModel
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (value == _selectedEmployee) return;
_selectedEmployee = value;
HolidayAllowance = _staffDataService.GetEmployeeHolidayAllowance(_selectedEmployee.Id);
RaisePropertyChanged();
RaisePropertyChanged(nameof(HolidayAllowance));
}
}
NoteViewModel
public RelayCommand SaveNoteCommand { get; private set; }
private void SaveNote()
{
var note = new Note
{
NoteContent = NoteContent,
EmployeeId = ViewModelLocator.Staff.SelectedEmployee.Id,
NoteDate = NoteDate
};
_dataService.SaveNote(note);
}
I'm using MahApps Flyouts to show the view for add note:
This is where the view is shown, it is launched from MainView.xaml NOT Staff.xaml, which I think is going to be another issue of getting SelectedEmployee ID:
MainView.xaml
<controls:Flyout Name="AddNoteFlyout"
Header="Add Note"
IsModal="True"
IsOpen="{Binding IsAddNoteOpen}"
Opacity="85"
Position="Right"
Width="450">
<views:AddNote VerticalAlignment="Top" Margin="0,30,0,0"/>
</controls:Flyout>
Im considering firing a message on the button click that launches the View, which my staff view would register against. The message would contain the selectedEmployeeId. Would that be a better way?
The simple way
The simple way is what you are doing, but maybe a bit better solution is to create a static or singleton class like a NavigationParameterContainer and store the selected StaffMember in a public property. Then you can retrieve it in your NotesViewModel
The best practice
The better solution for passing data between ViewModels is using a custom navigation service, and navigation aware ViewModels.
MVVMLight don't support this, so either you use a different framework like Prism or write yourself an architecture that you can use for making parameterized navigationt.
The base idea is that you create an INavigationAware interface that support navigation lifecycle callbacks like OnNavigatedTo, which receives an object representing the NavigationParamter (the selected StaffMember).
Then you create some kind of NavigationService with a Navigate method, that accepts some parameter to determine the Page you want to navigate to, and an object wich is the NavigationParamter.
When you navigate you call the Navigate method on your Service and pass the selected item as parameter. Then you need to make the actual navigation inside your service, and after the navigation is finished, you call the OnNavigatedTo callback on your ViewModel if it is implementing the INavigationAware interface. (You can retreive the VM from the Page.DataContext and cast it to INavigationAware if it is not null you can call the OnNavigatedTo).
Finally in your NotesViewModel you just need to implement the INavigationAware interface, and handle the parameter you received in the OnNavigatedTo method.
This is just the basic idea but I strongly recommend you to see some MVVM framework that already implements this. (like PrismLibrary).

UserControl design

I have to write a new UserControl for AccountManagement. To integrate in your application with several TabItems my UserControl has to implement the IModule interface which looks something like:
public interface IModule : INotifyPropertyChanged
{
FrameworkElement TabContent { get; }
ImageSource TabIcon { get; }
string TabTitle { get; }
}
I want to build the UserControl for AccountManagement with MVVM so I want to implement the IModule interface in my ViewModel.
My two questions are:
Is this a good solution, or should I prefer to implement IModule in my View-CodeBehind?
If I implement IModule in the ViewModel, how can I pass the View as TabContent to the parent?
No. FrameworkElement is a view concept and should not be in the ViewModel. I assume you will have to implement it in code-behind, but why don't you see how other tabs are written and ensure you are consistent with the existing codebase?
EDIT:
You will still need to implement the above in the View. All three properties are view based, with the possible exception of TabTitle which could pull its name from the view model. There is nothing stopping you moving toward MVVM for the content of the tab, but this existing requirement will have to be done in the view.
A good rule of thumb for deciding if something belongs in the VM is to consider whether you could unit test the VM without any view at all, or whether you could theoretically write a text based console view to drive the VM.

Display arbitrary controls on a WPF Window

I'm developing a WPF wizard WiX custom managed bootstrapper application.
Some elements of the wizard are common (for example, the product information and logo at the top), and I don't want to have to redefine these. I also don't want to have to redevelop the whole thing from scratch for each install (yes, there are a number of installs I want to use this with and they have different wizards).
I have a model like this (simplified to avoid confusion)
public class WizardModel : INotifyPropertyChanged
{
private UserControl _currentPage;
public UserControl CurrentPage
{
get { return _currentPage; }
set
{
_currentPage = value;
if (PropertyChanged != null) PropertyChanged(this, "CurrentPage");
}
}
}
I would like to bind a control so that the control in CurrentPage is displayed when the property changes.
I did experiment with using ContentCrontol but I quickly dismissed that as not intended for this purpose, and I'm really not sure where to go from here.
I'm making a few guesses here because there's not a lot of detail in the original question, but I think I get the idea of what you are trying to accomplish.
Seems like you would want to have a base class with all of the common controls on it already. Say we call this class WizardUserControl. Anyway, let's say we define a Grid inside the WizardUserControl (let's call it CustomGridArea) and that grid will be the main area where we can drop all of our custom controls based on which page of the wizard we are processing. So maybe WizardUserControl may implement a method like:
public class WizardUserControl : UserControl
{
...
// All your standard wizard code stuff/behavior/business logic/etc...
...
...
public void InsertCustomizedControl(UserControl customizedControl)
{
CustomGridArea.Children.Clear();
CustomGridArea.Children.Add(customizedControl);
}
}

wpf how to refresh bound viewmodel

I'm a newbie to WPF and C# and am building my first app mostly by using code examples. I'm sure there might be some better ways to do this, that I'm not understanding yet, so I'm coming to you guys for some advice.
I have a treeview control with of a bunch of nested objects that is downloaded into an ObservableCollection viewmodel from a WCF Service that I also built. I have the viewmodel declared in the Windows.Resources of the XAML.
My treeview then binds to that StaticResource by its key name.
Items="{Binding Source={StaticResource MyCatalogModel},Path=Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
The data in the tree is saved locally to a file. When the viewmodel is instantiated it reads the file, or it creates it if it doesn't exist.
I have 2 related problems right now.
Sometimes the data object that is imported is rather large with lots of nested objects (children). This is taking a long time to update the tree. How can I speed this up? Can I "turn off" the Notify changed stuff of the ObservableCollection, and just reload (rebind?) the viewmodel when it's finished?
I'd like to give the user the ability to basically clear out all the items from the tree and start from scratch. I have code that dumps the underlying file and as I said, it will be recreated when a new viewmodel is instantiated, but I don't know how to "reset" the binding of the resource and the tree. How do I do this?
Thanks to all who respond and any code snippets will be greatly appreciated!!
I had a similar problem, where I had a very large amount of data in a collection - and the event for OnPropertyChanged was firing for each item in the collection. I added an extension with a method to add a range to an ObservableCollection. Here is the code for the extension.
public class SmartCollection<T> : ObservableCollection<T>
{
public SmartCollection()
: base()
{
_suspendCollectionChangeNotification = false;
}
bool _suspendCollectionChangeNotification;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suspendCollectionChangeNotification)
{
base.OnCollectionChanged(e);
}
}
public void SuspendCollectionChangeNotification()
{
_suspendCollectionChangeNotification = true;
}
public void ResumeCollectionChangeNotification()
{
_suspendCollectionChangeNotification = false;
}
public void AddRange(IEnumerable<T> items)
{
this.SuspendCollectionChangeNotification();
int index = base.Count;
try
{
foreach (var i in items)
{
base.InsertItem(base.Count, i);
}
}
finally
{
this.ResumeCollectionChangeNotification();
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(arg);
}
}
}
So instead of an ObservableCollection its a SmartCollection. What I did was build my collection of objects into a List then you call the AddRange method and pass in your List of objects. This greatly improved the performance.
As far as recreating the tree - if its based on the viewmodel. Just new up the viewmodel that its bound to.
It's amazing how many times the same old questions come up again and again. Right, I have no idea why you are Binding to a StaticResource, but that is a bad idea.
Just create a public property of type ObservableCollection<T> in your view model, set an instance of it as the DataContext of your view in whichever way you prefer or know. Make sure you implement the INotifyPropertyChanged Interface correctly in the code behind, or declare a DependencyProperty instead. Then you can Bind directly to this property, let's call it Items:
<TreeView ItemsSource="{Binding Items}" ... />
When you have it set up this way, then all you need to do to empty or reset the TreeView is this (in the view model):
Items = new ObservableCollection<YourItemDataType>();
As for speed, it's hard to know what you're doing, but WPF is known for being slow when rendering large collections. I can't help with that, sorry.

Proper handling of events raised by child in MVVM parent

I am simplifying a little here:
I have a tab control, and should like for individual tabs to have the power to create further tabs; siblings if you will. So I am calling the tab control the parent, and its tab pages the children.
Using MVVM, my tab control view model is something like this:
class ParentViewModel
{
ObservableCollection<ChildViewModel> _pages;
public ObservableCollection<ChildViewModel> Pages
{
get
{
if (_pages == null)
_pages = new ObservableCollection<ChildViewModel>();
return _pages;
}
}
public ParentViewModel()
{
Pages.Add(new ChildViewModel());
}
}
So I have a collection of ChildViewModel objects on my ParentViewModel.
This works a treat, and from inside the ParentViewModel I can of course easily add all the extra ChildViewModel objects I want to my collection and have it all nicely reflected in my Views.
What I want to do is allow a button press (for example) on a ChildView to result in the addition of another ChildViewModel to the collection on the ParentViewModel object.
I have read a lot about relay commands, routed commands, relativesource bindings, the dependancy injection pattern and so forth, but could someone tell me please the 'proper' (in an MVVM sense) way to achieve this, and exactly how it is best done. Thank you!
One of the ways I like to handle a situation like this is with Event Aggregating.
It is an ability added with Unity (if you aren't already using it)
Basically you add the Event Aggregator to your Dependancy Injections and then your Parent would subscribe as a listener to the event and your children would publish the event.
The nice part about this is that the children have no concept of who is listening and the parent just knows it has an event request to handle. For more information you can go HERE!
You can use MVVM Lite Messenger class (or write you own):
And send a Message from a childe class to Parent class. See an example here.
create a message class:
public class AddNewChildMessage
{
public string Data {get;set;} //any data you need to pass
}
In a ParrentViewModel's constructor:
Messenger.Default.Register<AddNewChildMessage>
(
this,
( message ) => AddNewChild(message )
);
private void AddNewChild(AddNewChildMessage message)
{
//do staf with message.Data if any
Pages.Add(new ChildViewModel());
}
In a child view model:
Messenger.Default.Send<AddNewChildMessage>( new AddNewChildMessage() );
Adding childs is an example - you can add any logic you want.

Categories