Using Prism for navigation in Wpf application - wpf

I'm starting a new project in Wpf, and am now looking into using Prism. For now I'm simply trying to set up the navigation of the application using Prism. Unfortunately my lack of experience with the framework makes it a bit difficult to get started..
To be more precise about my first challenge I have an application with a "navigation/menu" region and a "main" region. The navigation region will be the same for all different main region views, and I therefore define the menu in the shell.xaml. When clicking any menu item I'd like to add a view to the region using Prism. First; is this something one typically will use Prism for? If so; what's the typical approach? And I mean on a more structural level..
My impression is that Prism will make my application much more scalable in the end, and I see that I get some other advantages from it - like the IoC container. So I would like to use it - if I could only get through the first steps..

I've got a sample that uses a little more of the CAG feel for modules contributing to a menu and how to add views to a region. It ought to make things a little more clear.
http://dl.getdropbox.com/u/376992/CAGMenus.zip
Hope this helps,
Anderson

Bumbuska,
Prism will be a great way for you to achieve this functionality and it is pretty easy to do once you understand the principals.
The way I will do it is to add event listeners in the start up event of your Main Region. When you select your item in the menu, you fire the event. When that happens your Main Region will handle the event and you clear the current view from the Main Region. Then create the new view you want to use and add it.
Your main region should look something like this:
public void Initialize()
{
Events.PageEvents.ClickedEvent1 ce1 = this.eventAggregator.GetEvent<Events.PageEvents.ClickedEvent1>();
ce1.Subscribe(LoadView, ThreadOption.UIThread, true);
}
private void LoadView(Events.HomePageEvents.Clicked clicked1)
{
IRegion mainRegion = RegionManager.Regions["MainRegion"];
foreach (object view in new List<object>mainRegion.Views))
{
RegionManager.Regions["MainRegion"].Remove(view);
}
IModule firstModule = Container.Resolve<Modules.FirstModule>();
firstModuleModules.Initialize();
}
I hope that points you in the right direction. Please let me know if you need any more info.

Related

Re-navigate to a particular view in multi-view region

I'm working on a WPF application that's utilizing the Microsoft Prism framework. One aspect of the application is a "modal" region that can hold any number of modal windows that overlays over the entire window. As more views are navigated into the region, each window slides to the right to allow the new window to occupy the center of the screen. Here's a more visual explanation:
When the "modal" region contains a single view:
When another view is added to the region:
When several more views are added:
I have this working using a custom control that manages the animation and display of its children. Here's what the control's custom RegionAdapter's Adapt method looks like:
protected override void Adapt(IRegion region, ModalContainer regionTarget)
{
region.ActiveViews.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler((o, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach(FrameworkElement element in e.NewItems)
{
regionTarget.AddChild(element);
}
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
foreach (FrameworkElement element in e.OldItems)
{
regionTarget.RemoveChild(element);
}
}
});
}
My question is this: What's the best way to navigate back to an earlier window? Right now, the only way I know to trigger the RemoveChild method above is to explicitly remove the view from the region, which requires that I keep a list of all the views currently in the region somewhere:
// to remove the most recently added view from the region
_regionManager.Regions["ModalRegion"].Remove(addedViews.Pop());
Ideally, I would be able to navigate backwards using Prism's "journaling" concept, but I don't see a way in my RegionAdapter to respond to when a view already in the region is re-navigated to.
Any hints would be much appreciated.
EDIT
I was able to achieve this functionality by following a suggestion by GOstrowsky (see the comments in the accepted answer) - I changed my region adapter to only maintain a single active view in the region (the view currently in the center of the screen). I then can target that view for removal via myRegion.ActiveViews.FirstOrDefault().
YET ANOTHER EDIT
I have since changed this implementation yet again, as we needed the ability to remove any of the views currently in the region, not just the last one. See the accepted answer for details.
If you would ocassionally want to Navigate back to a previous View, you should not remove it from the Region when Navigating from it.
Instead, you could just deactivate it from the OnNavigatedFrom() method. And then, use the NavigationJournal to navigate back.
Regarding your RegionAdapter control you could modify it so that it could handle View activation and de-activation. For example, you could Publish a De/ActivationChanged event from each ViewModel OnNavigatedFrom() and OnNavigatedTo(), and handle these events on your custom control by Subscribing to it and performing the corresponding task to each event.
You can find more information about Navigation and Event Aggregation in the following MSDN Prism Guide chapters:
8: Navigation
Event Aggregation
I hope this helps,
Regards.
Initially, I solved this problem by only allowing a single region to be active, which guaranteed that removing the region's currently active view always removed the view currently in the center of the screen. However, since then, we've needed the ability to remove any of the views from the region, not just the first. To accomplish this, I realized that the Region.Views property can be cast to a List and then accessed by index:
List<object> allViews = modalRegion.Views.ToList<object>();
I'm a little uncomfortable with this solution, since the IViewsCollection definition inherits from IEnumerable, not IList; technically I could be handed a custom IViewsCollection that cannot be cast to an IList.... but in the short term I'm going to run with this.

MvvmLight Good Practice : Show a Form by a ViewModel

I'm a fresher with WPF, MVVM. Now, I have been investigating MVVM Light. I got some confused. I appreciate any help from all of you. Thanks in advance for taking a look at this post.
Can we absolutely remove code behind of View (such as: Invoking InitializeComponent() somewhere inside .xaml --> Does not need view.xaml.cs anymore in some simple case).
I have many views insight my project, how many locator is necessary?
I intend to make separate locator for each view. And I wonder that if I register all locators in app.xml, are all of views initialized and registered right after user run the application? If Yes, Is it not good for performance?
Main question:
I have a scenario:
I have many forms: such as :
MainForm: which is the 1st form invoked by application.
ImportForm: which is invoked when User click Import (from MainForm)
Assumption that: I did finish all stuff related to binding (such as button Import --> RelayCommand(OnImport))
What is the best practice for me to implement this scenario?
1. Just implement to init and show ImportForm like below:
public void OnImport()
{
ImportForm importForm = new ImportForm();
importForm.ShowDialog();
}
It's simple, but I wonder if this way follow the MVVM's paradigm?
So, I did some researching and do another way like:
public void OnImport()
{
//// Just send message
Messenger.Default.Send(
new NotificationMessage(this, "OnImport"));
}
In Code Behind: MainForm.xaml.cs
public MainForm()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
if (nm.Sender == this.DataContext)
{
if (nm.Notification == "OnImport")
{
ImportForm importForm = new ImportForm();
importForm.ShowDialog();
}
}
});
}
By this way, I must write some code inside code behind --> Is it a problem?
Both above solutions can finish mentioned scenario correctly, but I confused which one is better or is there any actually right solution out there?
Thanks again for your patience with the long question.
No, you cannot. InitializeComponent() paints UI on the screen. The purpose of MVVM is to separate logic that does not related to View and store it in a ViewModel. It does not tend or aim to remove code-behind.
It depends on you. You can create one Locator for all ViewModels or one Locator per one ViewModel. Anyway, I found that Locator does not scale well and hard to manage in a larger project. It creates dependency between View, Locators and ViewModels. I personal prefer to use DI framework instead of Locator even if it is a small project.
You can do both, depends on your requirement. (a) If clicking the button on the main form does nothing more than show a dialog then I would use Click event because it is View related. It has nothing to do with any logic, so keep it in the code behind is the best solution for me. (b) By the way, if clicking the button does something, for example, connect to a database then show a dialog if a condition is true. In this case, I would use Messenger to keep View and ViewModel separate from each other.

How do I solve the following design in the MVVM paradigm? (newb)

I'm still starting out with MVVM and read through all the MSDN Prism examples but I'd nonetheless like some guidance with the following design problem which I feel will help me latch onto the Prism concepts and reinforce what I just learnt
I have a multi-window application - each window has a filter drop down control. The value of this filter control will affect the display of other controls within that same window, eg grids, charts etc.
Certain interactions (eg double clicking a row on a grid) will spawn another window which will have its own separate filter control, which will similarly affect the display of other controls only within that window
How do I implement this behavior of the filter driving the display of other user controls it has no idea of and have it restrict its behavior to only the window hosting it? How do I keep these interactions loosely coupled?
I'm thinking I need to use the EventAggregator and for the filter control to publish an update event when the selection changes? Am I thinking about this the right way? Would each window need a separate EventAggregator?
Yes, you are thinking about this the right way. The EventAggregator is a good tool for what you're doing. You'll need to have the EventAggregator on every window you plan to raise an event from. You can inject the EA into your constructor or use the ServiceLocator. Here are 2 examples:
// Ctor injection
private IEventAggregator _eventAggregator;
public ViewModelBase(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
// Service Locator
ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<YourEvent>().Publish();
Now, you'll need to create a CompositePresentationEvent for the EA to publish. You can minimize the number of these that get created by including a payload in the CPE. Something like this:
public class NavigationSelectedEvent : CompositePresentationEvent<NavigationEnum.Destination>{}
So now you're ready to publish the event:
_eventAggregator.GetEvent<NavigationSelectedEvent>().Publish(NavigationEnum.Destination.Home);
And then subscribe to it - using an optional filter on the payload so you're not wasting resources:
this.EventAggregator.GetEvent<NavigationSelectedEvent>().Subscribe(HandleNavigationEvent, ThreadOption.UIThread, false, i => i == NavigationEnum.Destination.Home);
You should publish the filter notification using the EventAggregator, that is the best way to do it from my experience. You should be using only one EventAggregator to serve as a hub between all the subscribing objects.
Something like:
MyNotificationChangedArgs args = new MyNotificationChangedArgs();
args.Payload = GetThePayload(someParameters);
myEventAggregator.GetEvent<NotificationFilterChangedEvent>().Publish(args);
Another thing that really helps is to dependency-inject the EventAggregator, for instance via Unity. This way all of your controls can simply access the common EventAggregator by calling on the UnityContainer.Resolve method:
var myEventAggregator = myUnityContainer.Resolve<MyEventAggregator>();
Every View has access to the Model through the ViewModel. Model your filters. Then the Views bind to ViewModel representations that use the filters. Your Views don't have to know about each other, they just have to bind to the Model.

WPF/MVVM - how to execute commands on start-up?

I'm writing a WPF app following MVVM principles.
I wan't to execute a command on startup - I'm in doubt as to what is the best method?
The ViewModel should be indifferent as to whether there are any views, right?
Is it then 'ok' to do stuff as the last thing in VM constructor? Or is there an event for 'application is now up and running, all initialization has completed' that I can hook onto?
It feels wrong to wire it into the view model?
To be a bit concrete, I'd like to do as Visual Studio and load 'most recent solution' if the user has selected that in preferences. If the user manually loads a solution through GUI I start the flow in a MainFormViewModel and I could handle the load in last lines of constructor there?
Any thoughts?
Anders, Denmark
The Windows.Interactivity approach or asynchronously loading their preferences from the constructor are equally viable - an alternative exists if your using the MefedMVVM framework.
You can also import the IContainerStatus to attach to the view being loaded entirely from the view model (and therefore nothing to forget doing in the XAML) More info is available here
I really wanted to avoid calling methods from within the ViewModel constructor - and in my view activating events from constructor is doing just that (or at least when using Prism as I am (should have mentioned that).
I ended up doing the simple thing and simply calling a Loaded method on my main ViewModel after construction has ended.
Nonetheless, Scott singled out as the answerer - thank you all for taking the time. I appreciate your point of view even if I chose another way in the end.
Anders, Denmark
var mainViewProvider = ObjectFactory.GetInstance<IMainViewProvider>();
var mainWindowViewModelProvider = ObjectFactory.GetInstance<IMainWindowViewModelProvider>();
var mainWindow = mainViewProvider.GetView();
var mainWindowViewModel = mainWindowViewModelProvider.GetViewModel();
mainWindow.DataContext = mainWindowViewModel;
mainWindowViewModel.Loaded(Settings.Default.LoadLatestOnStart);
mainWindow.Show();
What you can do is use your MainForm's (the one which needs to load the solution) Loaded event.
Use Windows.Interactivity EventTrigger to attach a command to Loaded event. And on that command's execution, load the solution.
I would introduce Controllers which are responsible for the UI workflow. They know when the application has started and they can execute Commands. This doesn't violate with the MVVM pattern. If you are interested how this work then you might find the sample applications of the WPF Application Framework (WAF) interesting.
That's ok if and when you setup the context in code, but not if you do it from xaml - for the sake of transparency and flexibility one should consider supporting both code and xaml.
Another approach could be to trigger something on your model from xaml when certain criteria has been met.
I believe a storyboard could be used for this approach.
/Torben Falck, Strongminds, Denmark, www.strongminds.dk

WPF Prism V2 Using M-V-VM - Add a view at runtime to a region from the ViewModel

Hopefully quite a simple one, having my first try at WPF with Prism V2 using M-V-VM and so far finding everything pretty awsome. My Shell is pretty simple, Ribbon Control at the Top, DataGrid of Help desk tickets on the left, and a TabControl on the right.
When a user opens the selected ticket from the datagrid, I want the Ticket to open as a Tab on the Tab Control. I know in order to do that I need to add and then activate the View to the region using the RegionManager. But doing this from the ViewModel doesn't seem correct to me, although I could do it using DI (DepenecyInjection) it still rings alarms in my head about giving the ViewModel some knowledge about a View.
To Add to this, different modules will also be adding other views (Contact, Client etc) into the TabControl, I'd like to use DataTemplates to get the TabControl to display the View Correctly, can anyone give me any pointers for this as well.
Many Thanks
Ben
Full answers please, not just links. It's what StackOverflow is for!
MVVM + Services = Ultimate Power!
A service is just an interface that's well known and is registered in your IOC container. When the ViewModel needs to do something outside of itself, like say open a tabbed document, it uses the service. Then the service is implemented as needed for the particular program.
For example:
public interface IDocumentService
{
void OpenDocument(IViewModel viewModel);
}
internal class DocumentService:IDocumentService
{
public void OpenDocument(IViewModel viewModel)
{
// Implement code to select the View for the ViewModel,
// and add it to your TabControl.
}
}
{
// Somewhere in your ViewModel...
// Make sure you can get the IDocumentService
IDocumentService docService = ioc.Get<IDocumentService>();
docService.OpenDocument(new TicketViewModel());
}
Commands are the way to do this - you'll send a command to yourself, called "RequestBringTicketIntoView"; it will bubble up to the Window, where you handle it. Read Josh Smith's article:
http://joshsmithonwpf.wordpress.com/2008/03/18/understanding-routed-commands/

Resources