0
votes

I am using WPF/PRISM/MEF for a desktop application.

This is a simple test application that has three regions. The views are defined in an external module.

It appears I need to set my shell DataContext. So I set it to a view model as illustrated below - and the application behaves properly.

I'm not satisfied with making the explicit definition. Is it not possible during initialization, to load some module, and to find some view and assign that to my shell's DataContext? Where do I find the documentation - I must have missed it in the Developers Guide (and in the sample apps). Or, does someone have a suggestion?

Shell.xaml:

<Window x:Class="TestMenuTaskbarDT.Shell"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:prism="http://www.codeplex.com/prism"   

  xmlns:local_viewmodels="clr-namespace:TestMenuTaskbarModuleMain.ViewModels;assembly=TestMenuTaskbarModuleMain"
    . . .
>

  <Window.InputBindings>
    <KeyBinding Key="S" Modifiers="Control" Command="{Binding Path=CloseProjectCommand}" />
    <KeyBinding Key="O" Modifiers="Control" Command="{Binding Path=OpenProjectCommand}" />
  </Window.InputBindings>
  <StackPanel>
    <ItemsControl Name="MainMenuRegion" prism:RegionManager.RegionName="MainMenuRegion" />
    <ItemsControl Name="MainToolbarRegion" prism:RegionManager.RegionName="MainToolbarRegion" />
    <ItemsControl Name="MainContentRegion" prism:RegionManager.RegionName="MainContentRegion" />
  </StackPanel>
  <!-- How does one set the datacontext, when it should be dynamically loaded? -->
  <Window.DataContext>
    <local_viewmodels:MainWindowViewModel />
  </Window.DataContext>

</Window>

(or should local_viewmodels ... be put in a resource and somehow set there?)

Can I then put something like the following in the Bootstrapper? Or is there a different technique altogether?

Bootstrapper.cs :

. . .
class Bootstrapper : MefBootstrapper
{
    . . .
  protected override IModuleCatalog CreateModuleCatalog()
  {
      // All dlls are expected to reside in the same directory as the *.exe
      return new DirectoryModuleCatalog() { ModulePath = System.AppDomain.CurrentDomain.BaseDirectory };           
  }

  protected override void ConfigureAggregateCatalog()
  {
      base.ConfigureAggregateCatalog();
      this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
      // Module registration remains the same *IT* registers the views with the region catalog
      this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(TestMenuTaskbarModuleMain.TestMenuTaskbarModuleMain).Assembly));
  }
    . . .

  protected override void InitializeShell()
  {
    base.InitializeShell();
    Application.Current.MainWindow = (Window)this.Shell;
    // Is something like the following possible?
      _MyBaseViewModel = GetAViewModelSomehowFromAModule("MyViewModelKey");
      Application.Current.MainWindow.DataContext = _MyBaseViewModel;
    //
    Application.Current.MainWindow.Show();                 // Displays MainWindow

    }
2
(or should local_viewmodels ... be put in a resource and somehow set there?), look at a topic of View Model Locator.AutoWireViewModeladminSoftDK
nice tip - Need to extend my vocabulary. Googling ViewModelLocator AutoWire gave me a number of Solutions that work with SilverLight, or .net 4.5 or Prism 5.0. (They seem to need CompositionIntializer/ExportFactory) The new ViewLocator seems to be just what I want - and would enable almost all of it in XAML - only I'm currently using VS2010, .Net 4.0, Prism 4.1 on a Desktop. I'll search further for something old-fashioned, but could still use anothter tip or so.infowanna
You could also import your view model as dependency injection in your Shell, something like [ImportingConstructor] public Shell(MyViewModel viewModel) { this.DataContext = viewModel; }dymanoid
Ahh! This link helped me out: reedcopsey.com/2010/03/26/mef-compositioninitializer-for-wpf and, oddly enough, the MSDN docs here: msdn.microsoft.com/en-us/library/… . I used an Import/Export pair from the different modules and MEF for the Desktop was able to apply the appropriate class to the Shell DataContext. I made the Shell DataContext use a DataBinding which meant adding a property - that's okay, I suppose. I got hung up on the term "CompositionInitializer" which only applies to Silverlight.infowanna

2 Answers

1
votes

In my Shell.xaml.cs I'd have this property defined:

[Import]
ShellViewModel ViewModel
{
    set
    {
        this.DataContext = value;
    }
}

And then in my ShellViewModel class the constructor would be adorned like this ...

[Export]
public class ShellViewModel

So relying on the MEF composition import/export attributes to achieve DataContext setting. This is a simplified example but you might want to look into it further with regards to composition plurarity errors and such-like.

0
votes

If I understand Dynamoid, I should use the following:

namespace TestMenuTaskbarDT
{     
  [Export]
  public partial class Shell : Window
  {
    [ImportingConstructor] public Shell([Import("ShellViewModel")]object aShellViewModel)
    {
        InitializeComponent();
        //NOTE: DataContext is magically set from the imported object "aShellViewModel."
        // I assume MEF uses Reflection to magically resolve all internal references.
        DataContext = aShellViewModel;
    }

    /* removed, ImportingConstructor does it all in one step.
    Note: DataContext is magically set from "value."
    [Import("ShellViewModel")]
    public object ViewModel
    {
        set { this.DataContext = value; }
    }
     */
}

}

Clearly, one-step construction with initialization is simpler than the two-step form of first construct then initialize. (So people should click on dynamoids comment to give him his due - not just for his hint but also for his kind persistence in getting his point across.)

(The only thing left here is to get it to display properly at design time - but that's a different muddle.)