WPF/MVVM Load an UserControl at Runtime

2019-03-14 04:20发布

问题:

i know that there a many articles about my problem but i cant find a solution. I am new in WPF - MVVM and i try to understand the MVVM-Logic. So i made a little project to understand that. For my later apps i want to load UserControls dynamicly to my Window.

In my StartView i have a Binding to the StartViewModel. (The Binding is in the APP.xaml)

StartView app = new StartView();
StartViewModel context = new StartViewModel();

The StartView

<Window x:Class="test.Views.StartView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:test.ViewModel"
        Title="Window1" Height="300" Width="516">
    <Grid>
        <Menu IsMainMenu="True" Margin="0,0,404,239">
            <MenuItem Header="_Einstellungen">
                <MenuItem Header="Server" />
            </MenuItem>
        </Menu>
        <ContentControl Content="{Binding LoadedControl}" Margin="0,28,0,128" />
    </Grid>
</Window>

the StartViewModel

namespace test.ViewModel
{
    public class StartViewModel : ViewModelBase
    {
        #region Fields
        private UCStastistikViewModel _loadedControl;
        #endregion

        public StartViewModel()
        {
            LoadedControl = new UCStastistikViewModel();
        }

        #region Properties / Commands
        public UCStastistikViewModel LoadedControl
        {
            get { return _loadedControl; }
            set
            {
                if (value == _loadedControl)
                    return;

                _loadedControl = value;
                OnPropertyChanged("LoadedControl");
            }
        }
        #endregion

        #region Methods

        #endregion
    }
}

UCStatistikView

<UserControl x:Class="test.Views.UCStatistik"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:test.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="188" d:DesignWidth="508">
    <UserControl.DataContext>
        <vm:UCStastistikViewModel />
    </UserControl.DataContext>
    <Grid Background="red">       
    </Grid>
</UserControl>

UCStatistikViewModel

namespace test.ViewModel
{
    public class UCStastistikViewModel : ViewModelBase
    {
        #region Fields
        #endregion

        public UCStastistikViewModel()
        {
        }

        #region Properties / Commands
        #endregion

        #region Methods
        #endregion
    }
}

Now i want to load my UCStatistikView in the ContentControl of my StartView. But in the Startview only the Path test.UCStatistikViewModel is shown instead of the whole UC Can anybody give me some Ideas where my problem is / where im am going wrong ?

Bye j

回答1:

Your ViewModels should not care about UserControls. Instead, have them hold ViewModels, and let WPF resolve how to draw the ViewModel with a DataTemplate.

For example,

<Window x:Class="test.Views.StartView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:test.Views"
        xmlns:viewmodels="clr-namespace:test.ViewModel"
        Title="Window1" Height="300" Width="516">

    <Window.Resources>
        <DataTemplate DataType="{x:Type viewmodels:UCStastistikViewModel}">
            <views:UCStastistikView />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Menu IsMainMenu="True" Margin="0,0,404,239">
            <MenuItem Header="_Einstellungen">
                <MenuItem Header="Server" />
            </MenuItem>
        </Menu>
        <ContentControl Content="{Binding LoadedControl}" Margin="0,28,0,128" />
    </Grid>
</Window>

Also, get rid of the <UserControl.DataContext> in your UserControl. The DataContext should be passed in by whatever is using the control, not defined in the UserControl :)

Edit

Based on your comment to an earlier answer about switching out the content of the StartPage by switching a ViewModel, you may be interested in looking at this post of mine. It's titled Navigation with MVVM, however the same concept applies for switching Views or UserControls

Basically, you'd make the property LoadedControl of type ViewModelBase instead of hard-coding it's type, and then set it to whatever object you want displayed in your ContentControl. WPF's DataTemplates will take care of hooking up the correct View for the ViewModel.



回答2:

WPF doesn't support automatic resolution of the view for a given view model. The naive solution to your problem would be to directly add the UCStatistikView to your StartView and bind the VM to it

<Window x:Class="test.Views.StartView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="clr-namespace:test.ViewModel"
    Title="Window1" Height="300" Width="516">
<Grid>
    <Menu IsMainMenu="True" Margin="0,0,404,239">
        <MenuItem Header="_Einstellungen">
            <MenuItem Header="Server" />
        </MenuItem>
    </Menu>
    <UCStatistikView DataContext="{Binding LoadedControl}" Margin="0,28,0,128" />
</Grid>

A more elaborated approach would be to implement a ViewLocator (view model first approach) or a ViewModelLocator (view first approach). The locator automatically locates and binds the view(s) to a view model. There are some MVVM frameworks/toolkits out there which implement such a locator.

  • Caliburn.Micro: Offers a flexible ViewLocator and ViewModelLocator based on naming conventions. Here is an article about them
  • MVVM Light: Offers a ViewModelLocator. Here is an introduction

With Caliburn.Micro you start view would look like this

<Window x:Class="test.Views.StartView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="clr-namespace:test.ViewModel"
    Title="Window1" Height="300" Width="516">
<Grid>
    <Menu IsMainMenu="True" Margin="0,0,404,239">
        <MenuItem Header="_Einstellungen">
            <MenuItem Header="Server" />
        </MenuItem>
    </Menu>
    <ContentControl cm:View.Model="{Binding LoadedControl}" Margin="0,28,0,128" />
</Grid>

The cm:View.Model="{Binding LoadedControl}" attached property tells caliburn to locate the view for the view model which is bound and set the Content property of the ContentControl to it.



回答3:

Here's what you should do:

The StartView:

<Window x:Class="test.Views.StartView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:test.ViewModel"
        Title="Window1" Height="300" Width="516">
    <Grid>
        <Menu IsMainMenu="True" Margin="0,0,404,239">
            <MenuItem Header="_Einstellungen">
                <MenuItem Header="Server" />
            </MenuItem>
        </Menu>
        <UCStatistikView x:Name="myUCStatistikView" Margin="0,28,0,128" />
    </Grid>
</Window>

the StartViewModel:

namespace test.ViewModel
{
    public class StartViewModel : ViewModelBase
    {
        private UCStastistikViewModel _myControlViewModel;

        public StartViewModel()
        {
            _myControlViewModel = new UCStastistikViewModel();
        }

        public UCStastistikViewModel MyControlViewModel
        {
            get { return _myControlViewModel; }
            set
            {
                if (value == _myControlViewModel)
                    return;

                _myControlViewModel = value;
                OnPropertyChanged("MyControlViewModel");
            }
        }
    }
}

UCStatistikView:

<UserControl x:Class="test.Views.UCStatistik"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:test.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="188" d:DesignWidth="508">
    <Grid Background="red">       
    </Grid>
</UserControl>

Code behind of your StartView:

this.myUCStatistikView.DataContext = ((StartViewModel)this.DataContext).MyControlViewModel;

After having tested different approaches, my conclusion is that the best way for datacontext bindings is the code-behind of the parent view in case you have userControls.

EDIT: ViewModel locators are fine for trivial examples but if your ViewModel must be instantiated dynamically (it's mostly the case when its constructor requires parameters), the you can't use it. I personally stopped using locators because of this.



回答4:

Review :- http://patelrocky1608.wordpress.com/2013/12/26/how-to-add-custom-control-dynamically-in-wpf/

Which Contain Whole Code For Understand UserControl Dynamically..



标签: c# wpf mvvm