动态用户控件更改 - 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
Dynamic user control change - WPF
提问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 notObservableObjectunless you have a compelling reason to not useViewModelBasewhen using MVVMLight. KeepObservableObjectinheritence 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 setterprivateto 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财产SelectedIndex的ComboBox。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];
}
}

