WPF:如何使视图动态化?

时间:2017-12-06 10:45:20

标签: c# wpf xaml mvvm

我有以下xaml:需要在运行时必须填充以下xaml动态,但是如何? MainWorkspaceViewModel具有名为“View”的属性。此属性的类型为object,因此我可以设置其中的每个视图。

<UserControl x:Class="DesignerWorkspace.Views.MainWorkspaceView"
             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:local="clr-namespace:DesignerWorkspace.Views"
             xmlns:vm="clr-namespace:DesignerWorkspace.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">        
    <Grid>
        <ContentControl Content="{Binding View}"/>
    </Grid>
</UserControl>

2 个答案:

答案 0 :(得分:1)

有无数种方法可以做到这一点。 首先,您必须确定是否正在使用任何为您执行此操作的工具包。 在确定之后,只需检查此工具包的设置方式以及如何使用它。

有一种非常简单的方法可以做到这一点没有任何工具包以及我要向您展示的那个,以便您了解它是如何工作的。

请注意,我的示例是如何从新的创建,可能不是你的情况,但它只是理解(随意创建一个新的,看看它是如何工作的)

首先创建一个实现INotifyPropertyChanged的ViewModelBase,最好创建这个抽象类 ViewModelBase类

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }


    public virtual ICommand NavigateCommand => new RelayCommand(Navigate);

    protected virtual void Navigate(object param)
    {

    }

}

然后创建您的MainViewModel,让它继承ViewModelBase,并为其创建一个属性,以便为其分配ViewModel

public class MainViewModel: ViewModelBase
{
    private ViewModelBase _currentViewModel;


    public ViewModelBase CurrentViewModel
    {
        get => _currentViewModel;
        set
        {
            if (_currentViewModel == value)
                return;
            _currentViewModel = value;

            OnPropertyChanged(nameof(CurrentViewModel));
        }
    }

    public MainViewModel()
    {

    }

    protected override void Navigate(object args)
    {
        var namespaceName = "YourNameSpace.";
        var className = args.ToString();
        var fullClassName = string.Concat(namespaceName, (string)className);
        if (string.IsNullOrEmpty(fullClassName))
            return;
        var tipo = Type.GetType(fullClassName);
        if (tipo == null)
            return;

        var myObj = Activator.CreateInstance(tipo) as ViewModelBase;
        if (myObj != null)
            CurrentViewModel = (ViewModelBase)myObj;

    }

}

你应该注意MainViewModelViewModelBase(导航)的重写方法,这个方法将用于从一个简单的参数打开其他ViewModels,一个带有名称的字符串想要打开的ViewModel

现在您添加到MainWindow您的内容,在我的示例中,菜单以及将使用ViewModels填充的内容

<Window x:Class="YourNameSpace.MainWindow"
        .......
        Title="MainWindow"
        DataContext="{StaticResource MainViewModel}" >
<Grid>
    .....
    <!--In the Menu, how to open a View -->
    <MenuItem Header="Open ViewA" Command="{Binding NavigateCommand}" CommandParameter="ViewModelA"  />
    .......
    <!-- Your Content Control  -->
    <ContentControl  
            HorizontalAlignment="Center"
            VerticalAlignment="Top"
            HorizontalContentAlignment="Center"
            VerticalContentAlignment="Top"
            ClipToBounds="True"
            Content="{Binding CurrentViewModel}" >
    </ContentControl>
.....
</Window>

制作您的观点。 例如:

<UserControl x:Class="YourNameSpace.ucViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <Grid Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>


        <Grid  Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Content="this is the ViewA" Margin="5" />
            <Button Grid.Column="2" Content="Open View B" 
                    Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" 
                    CommandParameter="ViewModelB" Margin="5" />
        </Grid>

        <DataGrid Grid.Row="2" ColumnWidth="*"  Margin="5"
                                   IsReadOnly="True"
                                   AutoGenerateColumns="False"
                                   SelectionMode="Single" 
                                   HorizontalContentAlignment="Center" 

                                   ScrollViewer.CanContentScroll="True"
                                   ScrollViewer.VerticalScrollBarVisibility="Auto"
                                   ScrollViewer.HorizontalScrollBarVisibility="Auto">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Column 1"   MinWidth="50" Width="Auto"/>
                <DataGridTextColumn Header="Column 2"   MinWidth="140" Width="Auto"/>
                <DataGridTextColumn Header="Column 3"   MinWidth="240" Width="*"/>
                <DataGridTextColumn Header="Column 4"   MinWidth="70" Width="Auto"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

现在在app.xaml文件中,让我们说哪个ViewModel属于哪个UserControl

<Application x:Class="YourNameSpace.App"
             ...
             xmlns:local="clr-namespace:YourNameSpace"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <local:MainViewModel x:Key="MainViewModel"  />

        <DataTemplate DataType="{x:Type local:ViewModelA}">
            <local:ucViewA />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:ViewModelB}">
            <local:ucViewB />
        </DataTemplate>

    </Application.Resources>
</Application>

从另一个视图打开视图使用以下内容:

<Button Content="Open View A" 
        Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" 
        CommandParameter="ViewModelA" Margin="5" />

编辑:我忘了说,你的ViewModel必须至少有一个构造函数(空),所以不会出现问题

答案 1 :(得分:1)

最小的是添加一个上下文,其中包含视图和属性更新的更新。

然后,您可以从代码中的其他位置通过设置新视图来管理显示的视图。

这是一个简单的实现,可能需要添加缺少的检查。

class MainWorkspaceViewModel : INotifyPropertyChanged
{
    private object _view;
    public object View {
        get { return _view; }
        set { _view = value; OnPropertyChanged(); }
    }
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

internal class MyViewManager
{
    internal static MainWorkspaceView MakeMainView()
    {
        var view = new MainWorkspaceView();
        view.DataContext = new MainWorkspaceViewModel();
        return view;
    }

    internal static void UpdateView(MainWorkspaceViewModel viewmodel, object _next)
    {
        viewmodel.View = _next;
    }
    internal static void UpdateView(MainWorkspaceView view, object _next)
    {
        (view.DataContext as MainWorkspaceViewModel).View = _next;
    }
}