Search code examples
c#wpfmvvmmef

MVVM MEF WindowFormHost


I am currently trying to design an application that loads viewmodels through MEF imports.

So far so good, I navigate from viewmodel to viewmodel, having loaded each vm datatemplate through dictionaries.

Each time I navigate, I modify the content of the main contentPresenter in my Shell (MainWindow).

One of the viewmodel allows me to display a WindowFormHost for an activeX control (such as acrobat reader for example). Since WindowFormHost does not allow binding, I created the windowFormHost in the viewmodel and binded it to a ContentPresenter in the view.

And here is where it fails : when coming back to the same viewmodel, the view is created again... throwing a “Element is already the child of another element.” error.

How can I prevent that ? Should I unload WindowFormHost when view is reloaded ? Or Can I keep view instances so that I keep only one instance for each view and let data binding update controls ? (It looks better for memory consumption).

Thanks for your help !

[EDIT]

Loaded dictionary :

<DataTemplate x:Shared="False" DataType="{x:Type vm:DAVPDC3DVIAControlViewModel}">
    <vw:MyUserControl />
</DataTemplate>

View :

<DockPanel>
    <ContentControl Name="WFH3DVia" Content="{Binding Path=Control3DVIA, Mode=OneWay} </ContentControl>"
    <!--<WindowsFormsHost Name="WFH3DVia"></WindowsFormsHost>-->
</DockPanel>

VM (singleton, mef module) :

[Export(typeof(IDAVPDC3DVIAControl))]
public partial class DAVPDC3DVIAControlViewModel : ViewModelBase, IViewModel, IPartImportsSatisfiedNotification

VM (main window)

[Export]
public class MainWindowViewModel : ViewModelBase, IPartImportsSatisfiedNotification

// CurrentUC binds main widow view to controller active viewmodel

    public IViewModel CurrentUC
    {
        get
        {
            return myAddinManager.CurrentVM;
        }
    }

Main view :

Controler (displays module on event) :

    private void ModuleReadyEventAction(string iModuleName)
    {
        if (null != this.Modules && this.Modules.Count() > 0)
        {
            foreach (var item in Modules)
            {
                IBaseModule ibasemodule = item as IBaseModule;
                if (null != ibasemodule)
                {
                    Type tp = ibasemodule.GetType();
                    if (0 == tp.Name.CompareTo(iModuleName))
                    {
                        CurrentVM = ibasemodule.GetViewModel();
                        break;
                    }
                }
            }
        }
    }

Solution

  • I'm also working on a project in WPF using Prism v4 and MVVM (except I'm using Unity). I also have at least two controls that I need to use which are Windows Forms controls that must be hosted in a WindowsFormsHost. Let me explain my thoughts on the process..

    It seems to me, that you are trying to avoid any code in your View's code behind. That's the only reason I can think of that you are moving your WindowsFormsHost into your ViewModel. I think that this is fundamentally the wrong approach. The WindowsFormsHost exists for the reason of displaying a graphical Windows Forms control. Therefore, it belongs in the view!

    Now, I understand the appeal of DataBindings. Trust me, I've wanted to able to DataBind many parts of my WindowForms control. Of course, to accept a WPF data binding the property must be a dependency property on a dependency object. The easiest solution, which is not unreasonable, is to simply add the code to configure your windows forms control in the code behind for your view. Adding your UI logic into your ViewModel is an actual violation of the MVVM design pattern, while adding code behind is not. (And in some cases is the best approach)

    I've seen possible hacks to try to get around this limitation. Including using "proxies" which inject a databinding, or perhaps extending WindowsFormsHost and adding DependencyProperties which wrap a specific hosted control's properties, or writing classes using reflection and trying to throw in windows forms bindings. However, nothing I've seen can solve the problem completely. For example, my windows forms control can contain other graphical components, and those components would need to support binding as well.

    The simplest approach is to simply synchronize your view with your viewmodel in your view's code behind. Your view model can keep the file or document that is open, filename, title, etc., but leave the display and display related controls up to the View.

    Last, let me comment more directly on your question. I would need to see how you are registering your View and ViewModel with the MEF Container and how you are navigating to understand why you are receiving that error. It would seem to me that either your view or view model is getting created more than once, while the other is not. Are these registered as singleton types? Regardless, I stand by what I said about not including the WindowsFormsHost in your ViewModel.