动态用户控件更改 - WPF

4

我在WPF中开发一个应用程序,需要根据用户在ComboBox上选择的内容,在运行时更改ContentControl的内容。

我有两个UserControls,并且我的组合框中存在两个项目,每个项目对应一个UserControl。

第一个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>

第二个用户控件:
<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>

在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>

我被指定为 MainView 的 DataContext,下面是对应的 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
}

在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);
    }
}

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

很抱歉我发布了大量代码并且我对此缺乏知识,但我已经长时间使用它并且无法解决此问题。 谢谢

1个回答

10

问题:

  • 首先,在ViewModel(UserControl)中不要创建与View相关的内容。这样做时,就不再是MVVM模式了。
  • 派生ViewModels从ViewModelBase而不是ObservableObject,除非您使用MVVMLight时有强制不使用ViewModelBase的理由。将观察对象ObservableObject继承给模型。为VM和M之间提供良好的分离
  • 接下来,您不需要将所有东西都变成一个ObservableCollection<T>,例如您的_pagesViewModel。因为您在View上没有对其进行绑定,所以这只是浪费。只需将其保留为私有列表或数组。检查类型与其他类似类型之间的区别。
  • 不确定这个,也许您将此代码片段作为演示而提取出来,但是不要使用边距来分隔网格中的项。因为您的布局实际上只有一个网格单元,而边距使项目不重叠。如果您不知道这个问题,请参阅WPF布局文章。
  • 编写UI应用程序时,请不要忘记面向对象编程原则,如封装等。当具有不希望View切换的属性(例如CurrentPageViewModel)时,请将属性设置器设为private以强制执行。
  • 不要过早地在View中使用代码后台。首先判断它是否仅与View相关。我在谈论您的ComboBoxSelectionChanged事件处理程序。您在此演示中的目的是切换保存在VM中的绑定ViewModel。因此,这不是View唯一负责的事情。因此,请寻找涉及VM的方法。

解决方案:

您可以从这里获取修复了上述问题的工作代码,并自行尝试。

点1-5只需要基本直接的更改。

对于第6个问题,我创建了一个SelectedVMIndex属性,它绑定到ComboBoxSelectedIndex。因此,当选择的索引变化时,该属性的setter在更新自身后还会更新CurrentPageViewModel,例如:

public int SelectedVMIndex {
  get {
    return _selectedVMIndex;
  }

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

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

    CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
  }
}

1
嗨 Viv,它完美地运行了!谢谢你。 还有一个问题,你说的“首先,不要在ViewModel(UserControl)中创建与View相关的内容。这样做就不再是MVVM了。”是什么意思? - vdefeo
4
我的意思是,“UserControl” 是一个视图控件。在 ViewModel 中创建它并不是真正遵循 MVVM。MVVM 断开了视图和 ViewModel 之间的关系和依赖。因此,在 ViewModel 中,尽量不要创建像 UserControl、Button、ComboBox 等这样的东西。甚至像 System.Windows.Visibility 这样的东西也应该在 VM 中避免使用,而是在 VM 中使用一个布尔属性,并使用转换器将其转换为 View 中的 Visibility。 - Viv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接