动态用户控件更改 - WPF

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16244240/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:41:51  来源:igfitidea点击:

Dynamic user control change - WPF

c#wpfmvvmuser-controls

提问by vdefeo

I'm developing an app in WPF and I need to change in runtime a content of a ContentControldepending than the user selected on ComboBox.

我正在 WPF 中开发一个应用程序,我需要在运行时更改ContentControl依赖于用户在ComboBox.

I have two UserControls and at my combo exists two itens, corresponding each one each.

我有两个 UserControl,在我的组合中存在两个 itens,每个对应一个。

First usercontrol:

第一个用户控件:

<UserControl x:Class="Validator.RespView"
         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" 
         mc:Ignorable="d" 
         d:DesignHeight="167" d:DesignWidth="366" Name="Resp">
<Grid>
    <CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top" />
    <ListBox Height="112" HorizontalAlignment="Left" Margin="12,43,0,0" Name="listBox1" VerticalAlignment="Top" Width="168" />
    <Calendar Height="170" HorizontalAlignment="Left" Margin="186,0,0,0" Name="calendar1" VerticalAlignment="Top" Width="180" />
</Grid>

Second usercontrol:

第二个用户控件:

<UserControl x:Class="Validator.DownloadView"
         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"                           
         mc:Ignorable="d" 
         d:DesignHeight="76" d:DesignWidth="354" Name="Download">     
<Grid>
    <Label Content="States" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
    <ComboBox Height="23" HorizontalAlignment="Left" Margin="12,35,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
    <RadioButton Content="Last 48 hs" Height="16" HorizontalAlignment="Left" Margin="230,42,0,0" Name="rdbLast48" VerticalAlignment="Top" />
    <Label Content="Kind:" Height="28" HorizontalAlignment="Left" Margin="164,12,0,0" Name="label2" VerticalAlignment="Top" />
    <RadioButton Content="General" Height="16" HorizontalAlignment="Left" Margin="165,42,0,0" Name="rdbGeral" VerticalAlignment="Top" />
</Grid>

At MainWindowView.xaml

在 MainWindowView.xaml

    <Window x:Class="Validator.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:du="clr-namespace:Validator.Download"
        xmlns:resp="clr-namespace:Validator.Resp"                
        Title="Validator" Height="452" Width="668" 
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
      <Window.Resources>
        <DataTemplate DataType="{x:Type du:DownloadViewModel}">
            <du:DownloadView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type resp:RespViewModel}">
            <resp:RespView/>
        </DataTemplate>
      </Window.Resources>
    <Grid>   

        <ComboBox  ItemsSource="{Binding Path=PagesName}" 
                   SelectedValue="{Binding Path=CurrentPageName}"
                   HorizontalAlignment="Left" Margin="251,93,0,0" 
                   Name="cmbType"                    
                   Width="187" VerticalAlignment="Top" Height="22"
                  SelectionChanged="cmbType_SelectionChanged_1" />
         <ContentControl Content="{Binding CurrentPageViewModel}" Height="171" HorizontalAlignment="Left" Margin="251,121,0,0" Name="contentControl1" VerticalAlignment="Top" Width="383" />
</Grid>
</Window>

I assigned to the DataContextof the MainView, the viewmodel below:

我分配给DataContextMainView的,下面的viewmodel:

public class MainWindowViewModel : ObservableObject
{
     #region Fields

    private ICommand _changePageCommand;

    private ViewModelBase _currentPageViewModel;
    private ObservableCollection<ViewModelBase> _pagesViewModel = new ObservableCollection<ViewModelBase>();        
    private readonly ObservableCollection<string> _pagesName = new ObservableCollection<string>();
    private string _currentPageName = "";

    #endregion

    public MainWindowViewModel()
    {
        this.LoadUserControls();         

        _pagesName.Add("Download");
        _pagesName.Add("Resp");
    }

    private void LoadUserControls()
    {
        Type type = this.GetType();
        Assembly assembly = type.Assembly;

        UserControl reso = (UserControl)assembly.CreateInstance("Validator.RespView");
        UserControl download = (UserControl)assembly.CreateInstance("Validator.DownloadView");

        _pagesViewModel.Add(new DownloadViewModel());
        _pagesViewModel.Add(new RespViewModel());
    }

    #region Properties / Commands

    public ICommand ChangePageCommand
    {
        get
        {
            if (_changePageCommand == null)
            {
                _changePageCommand = new RelayCommand(
                    p => ChangeViewModel((IPageViewModel)p),
                    p => p is IPageViewModel);
            }

            return _changePageCommand;
        }
    }

    public ObservableCollection<string> PagesName
    {
        get { return _pagesName; }            
    }

    public string CurrentPageName
    {
        get
        {
            return _currentPageName;
        }
        set
        {                
            if (_currentPageName != value)
            {
                _currentPageName = value;
                OnPropertyChanged("CurrentPageName");
            }
        }
    }

    public ViewModelBase CurrentPageViewModel
    {
        get
        {
            return _currentPageViewModel;
        }
        set
        {
            if (_currentPageViewModel != value)
            {
                _currentPageViewModel = value;
                OnPropertyChanged("CurrentPageViewModel");
            }
        }
    }

    #endregion

    #region Methods

    private void ChangeViewModel(IPageViewModel viewModel)
    {
        int indexCurrentView = _pagesViewModel.IndexOf(CurrentPageViewModel);

        indexCurrentView = (indexCurrentView == (_pagesViewModel.Count - 1)) ? 0 : indexCurrentView + 1;

        CurrentPageViewModel = _pagesViewModel[indexCurrentView];               
    }

    #endregion
}

On MainWindowView.xaml.cs, I wrote this event to do the effective change:

在 MainWindowView.xaml.cs 上,我写了这个事件来做有效的改变:

private void cmbType_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
    MainWindowViewModel element = this.DataContext as MainWindowViewModel;
    if (element != null)
    {
        ICommand command = element.ChangePageCommand;
        command.Execute(null);
    }
}

The app run ok and I inspected the application with WPFInspector and saw that the view changes when the combobox is changed internally, but the ContentControl still empty visually..

该应用程序运行正常,我使用 WPFInspector 检查了该应用程序,发现在内部更改组合框时视图会发生变化,但 ContentControl 在视觉上仍然为空。

Sorry about the amount of code that I posted and my miss of knowledge but I'm working with this a long time and can't solve this problem. Thanks

对我发布的代码量和我的知识缺失感到抱歉,但我已经使用了很长时间并且无法解决这个问题。谢谢

回答by Viv

Issues:

问题:

  • Firstly don'tever create View related stuff in the ViewModel (UserControl). This is no longer MVVM when you do that.
  • Derive ViewModels from ViewModelBaseand not ObservableObjectunless you have a compelling reason to not use ViewModelBasewhen using MVVMLight. Keep ObservableObjectinheritence for Models. Serves as a nice separation between VM's and M's
  • Next you do not need to make everything an ObservableCollection<T>like your _pagesViewModel. You do not have that bound to anything in your View's so it's just a waste. Just keep that as a private List or array. Check what a type actually does in difference to a similar other one.
  • Not sure about this one, maybe you pulled this code snippet as a demo, but do not use margins to separate items in a Grid. Your Layout is essentially just 1 Grid cell and the margins have the items not overlap. If you're not aware of that issue, Check into WPF Layout Articles.
  • Please don't forget principles of OOP, Encapsulation and sorts when writing a UI app. When having Properties like CurrentPageViewModelwhich you don't intend the View to switch make the property setter privateto enforce that.
  • Don't resort to code-behind in the View too soon. Firstly check if it's onlya View related concern before doing so. Am talking about your ComboBoxSelectionChangedevent handler. Your purpose of that in this demo is to switch the Bound ViewModel which is held in the VM. Hence it's not something that the View is solely responsible for. Thus look for a VM involved approach.
  • 首先不要在 ViewModel ( UserControl) 中创建与 View 相关的东西。当你这样做时,这不再是 MVVM。
  • 除非在使用 MVVMLight 时有令人信服的理由不使用,否则请从ViewModelBase和不派生 ViewModel 。保持模型的继承。作为 VM 和 M 之间的良好分离ObservableObjectViewModelBaseObservableObject
  • 接下来,您不需要让所有内容都ObservableCollection<T>像您的_pagesViewModel. 你没有绑定到你的视图中的任何东西,所以这只是一种浪费。只需将其保留为私有列表或数组即可。检查一种类型与其他类似类型的实际区别。
  • 不确定这个,也许你把这个代码片段作为演示,但不要使用边距来分隔网格中的项目。您的布局本质上只是 1 个 Grid 单元格,并且边距中的项目不重叠。如果您不知道该问题,请查看 WPF 布局文章。
  • 在编写 UI 应用程序时,请不要忘记 OOP、封装和排序的原则。当拥有这样的属性时CurrentPageViewModel,您不打算让视图切换,使属性设置器private强制执行该操作。
  • 不要过早在视图中使用代码隐藏。在这样做之前,首先检查它是否只是与视图相关的问题。我在谈论您的ComboBoxSelectionChanged事件处理程序。在本演示中,您的目的是切换保存在 VM 中的绑定视图模型。因此,这不是 View 全权负责的事情。因此,寻找一种涉及 VM 的方法。

Solution:

解决方案

You can get a working example of your code with the fixes for above from Hereand try it out yourself.

您可以从此处获取带有上述修复程序的代码的工作示例,然后自己尝试一下。

Points 1 -> 5 are just basic straightforward changes.

点 1 -> 5 只是基本的直接更改。

For 6, I've created a SelectedVMIndexproperty in the MainViewModel which is bound to the SelectedIndexof the ComboBox. Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModelas well such as

6,我创建了一个SelectedVMIndex在其绑定到该MainViewModel财产SelectedIndexComboBox。Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModelas well such as

public int SelectedVMIndex {
  get {
    return _selectedVMIndex;
  }

  set {
    if (_selectedVMIndex == value) {
      return;
    }

    _selectedVMIndex = value;
    RaisePropertyChanged(() => SelectedVMIndex);

    CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
  }
}