5

I'm new to WPF, but from what I've read, a correct way to build applications is to switch up views on the same window. What I mean is something like a "frame" with a menu and a workspace where views are shown.

So far I've been following this, http://jesseliberty.com/2011/01/06/windows-phone-from-scratch%E2%80%93mvvm-light-toolkit-soup-to-nuts-3/ but that is for WP7 and I can't use the NavigationService on a WPF app.

I could say that the simplest thing I want is, the mainwindow.xaml has a view on it that displays a button, when I press that button I want a new view to be displayed on the same window (and the old view to disappear).

What is the correct way of implementing something like that?

EDIT: This started by using mvvm-light, but eventually evolved to prism. See my last answer to further details.

dcharles
  • 4,822
  • 2
  • 32
  • 29
Silva
  • 270
  • 4
  • 16
  • Here is my answer where I explained how to do this using the `DataTemplateSelector`: http://stackoverflow.com/questions/5309099/changing-the-view-for-a-viewmodel/5310213#5310213. Also my solution can be applied to Silverlight after a few changes. – vortexwolf Apr 24 '11 at 09:39

2 Answers2

2

This is a great question - I had similar questions when I started using MVVM. I use just the Prism/CAL libraries from MS.

I believe what you're looking for is the idea of a Region in CAL. It's basically a named container control that presents things. Basically, you name a region in a top-level UI piece, like your main window. Your app probably has a small number of these: maybe a header, footer, and main window regions. (That's how I've done it anyways.). Then from code behind you can access the region through a region manager, clear it, and drop in your ViewModel. The ViewModel gets mapped to it's appropriate View and voila the new View appears.

From a coding standpoint, I've usually seen it broken up like this: You have some sort of a NavigationController that has a method or two from clearing the region and showing a new ViewModel->View and also has methods like GoToPageX(). This abstracts the region manager. Then you've got a ViewModel for each page and a View for each page. Each ViewModel takes in the NavigationController via dependency injection (but you can create new ones if you're not using DI). Then on the ViewModel, it exposes a Command which gets mapped to the button and calls the NavigationController.

Somewhere you've also got to register the ViewModels with the Views used to show them.

Here's an example of a NavigationController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Unity;
using KSheets.CoreModule.Presenter;
using Zephyr.Core.Logging;
using KSheets.CoreSheets.Sheet;
using System.IO;

namespace KSheets.CoreModule.Switch
{
    public class Switchboard : ISwitchboard
    {
        private ILoggingService<ISwitchboard> m_Logger;
        private IRegionManager m_RegionManager;
        private IUnityContainer m_UnityContainer;

        public Switchboard(
            ILoggingService<ISwitchboard> loggingService,
            IRegionManager regionManager,
            IUnityContainer unityContainer
            )
        {
            if (loggingService == null) throw new ArgumentNullException("loggingService");
            if (regionManager == null) throw new ArgumentNullException("regionManager");
            if (unityContainer == null) throw new ArgumentNullException("unityContainer");

            m_RegionManager = regionManager;
            m_UnityContainer = unityContainer;
            m_Logger = loggingService;
        }

        public void GoHome()
        {
            m_Logger.Log("Going home");

            var worksheetEditor = m_UnityContainer.Resolve<IWorksheetEditor>();
            worksheetEditor.Initialize();
            LoadView(RegionNames.EditorRegion, worksheetEditor);

            var batchExporter = m_UnityContainer.Resolve<IExportBatchPresenter>();
            LoadView(RegionNames.ExporterRegion, batchExporter);
        }

        private void LoadView(string regionName, object newView)
        {
            var region = m_RegionManager.Regions[regionName]; 
            var oldViews = region.Views;
            foreach (var oldView in oldViews)
                region.Remove(oldView);
            region.Add(newView);
            region.Activate(newView);
        }
    }
}

Here's an example of registering a View with a ViewModel programmatically. Many folks do this in XAML but you can also do it in code which IMO works better if you're using dependency injection as you can register your views and your dependency injection stuff all at the same time when your module loads.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace Zephyr.WPF.Utils
{
    public static class ResourceUtils
    {
        public static void RegisterView<T, U>()
        {
            DataTemplate template = new DataTemplate();
            FrameworkElementFactory factory = new FrameworkElementFactory(typeof(U));
            template.DataType = typeof(T).FullName;
            template.VisualTree = factory;
            Application.Current.Resources.Add(new DataTemplateKey(typeof(T)), template);
        }
    }
}

        private void RegisterViews()
        {
            ResourceUtils.RegisterView<WorksheetDisplay, WorksheetDisplayView>();
            ResourceUtils.RegisterView<ProblemSelector, ProblemSelectorView>();
            ResourceUtils.RegisterView<WorksheetEditor, WorksheetEditorView>();
            ResourceUtils.RegisterView<ExportBatchPresenter, ExportBatchView>();
        }

At the end of the day, you need a small bit of code that is UI aware (or a UI control that listens to messages sent from non-UI code that knows a small bit about the UI like region names) and allows you to glue the ViewModel into place. However, this code is usually super minimal and definitely requires no code-behind except for out of box components. MVVM is kind of a lot to take in when you actually have to implement it in WPF the first time; a steep learning curve for getting a simple app up and running.

J Trana
  • 2,150
  • 2
  • 20
  • 32
  • Thanks for you kind answer! You were spot on on what I wanted. I've been reading alot about prism and how to do stuff with it. I've found these great series. I think it covers up all the basics. However I've still trying to understand the basics. Another simple doubt I have right now is; - I need a regionManager - I also need a manager for the ViewModels; This is called IoC right? Which is about the same thing as a service locator right? Both these managers can be obtained thru DI or "services locators" like the one that mvvm-light provides right? Sorry about the noob questions :) Thanks! – Silva Apr 27 '11 at 06:30
  • http://www.developmentalmadness.com/archive/2009/11/02/mvvm-with-prism-101-ndash-part-5b-servicelocator-vs-depdency.aspx The series I was talking about... – Silva Apr 27 '11 at 06:49
  • Yeah, you're looking for is an IoC (inversion of control) container. Yep, very close to a ServiceLocator. I've used Unity before, it's pretty slick. Take your pick - only thing I'd say is I don't know if I'd start with MEF though, as it accomplishes something a little different. Once you get a screen or two up and running, the whole thing gets way easier... – J Trana Apr 28 '11 at 16:21
  • Hello again! I've spent the last few days learning prism, now it's time to use your switchboard solution. Unfortunately, I'm not sure on how to use it. I'm doing a modular structure for my program. I have a interface a shell and two other modules (a loginModule and a helloWorld, the latter kicks in after a sucessfull login). With the loadView from the switchboard I can load the new view and it works great, but only if I create the view. My problem is were should I put the switchboard since he needs to create the new views as well... If this is getting confusing, I'm sorry... – Silva May 04 '11 at 11:26
  • I forgot to add... Circular dependencies are becoming a issue with the way I'm trying to solve this problem. Why? Because I've put the switchboard on my infrastructure module and added references to get the Views from the other modules. This works to a point, but I feel the is a better way to do this. I think this problem is the reason of why you've showed the register views part, but I can't figure out how to work with that... Can someone help me please? Anyway, thank you all very much! – Silva May 04 '11 at 11:31
1

I came here to complement the answer by J-Trana.

He really got me curious for the Prism and I'm glad he did. This just rocks. I've spent the last few weeks reading about it, watching the docs and the quickstart examples that come with it. These were my main references.

So, about the solution...

Prism Regions was indeed what I wanted. The Switchboard provided by him was key in my implementation but I tweaked it a little.

First I passed the UnityContainer inside it, so I could get more flexibility. (Or so I think it gives me.) Second, I've created a method LoadModule (because I have a modular structure) like this.

public void LoadModule(string module) { 
            IModuleManager moduleManager = m_UnityContainer.Resolve(); 

            moduleManager.LoadModule(module); 
}

From what I got, when this LoadModule executes the Initialize() method of a my modules, which are something like this...

public void Initialize()
    {

        Switchboard switchboard = Switchboard.GetSwitchboard();

        IUnityContainer container = switchboard.GetCatalog();

        switchboard.LoadView(RegionNames.ShellMainRegion, container.Resolve<HelloWorldView>());

    }

And this works!

Some tips worth of note: - `//The property that provides context(ViewModel) to the view.

    [Dependency]
    public HelloWorldViewModel HelloWorldViewModel
    {
        set
        {
            this.DataContext = value;
        }
    }
  • That connects a ViewModel to a View as a property on the Views' code behind.
  • this.Container.RegisterType();
  • I register my modules this way:

` Type HelloWorldType = typeof(HelloWorldModule);
this.ModuleCatalog.AddModule(new ModuleInfo()

        {

            ModuleName = ModuleNames.HelloWorldModule,

            ModuleType = HelloWorldType.AssemblyQualifiedName,

            InitializationMode = InitializationMode.OnDemand

        }); `

- And my switchboard is on my infrastructure module. The View creation is shoot up to the module that has it and this solved my problem.

I hope this isn't too confusing, and I really would like some comments on how I'm doing this stuff... Am I doing this the "right" way? Is there a more elegant way? Stuff Like that...

Anyway, I hope this helps and motivates people that are trying to learn prism.

PS: How does this code tag work? And can I change the tags? mvvm-light makes no sense anymore.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Silva
  • 270
  • 4
  • 16