动态用户控件更改 - WPF [英] Dynamic user control change - WPF
问题描述
我正在 WPF 中开发一个应用程序,我需要在运行时更改 ContentControl
的内容,具体取决于用户在 ComboBox
上选择的内容.
I'm developing an app in WPF and I need to change in runtime a content of a ContentControl
depending than the user selected on ComboBox
.
我有两个 UserControl,在我的组合中存在两个 itens,每个对应一个.
I have two UserControls and at my combo exists two itens, corresponding each one each.
第一个用户控件:
<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
At 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如下:
I assigned to the DataContext
of the MainView, the viewmodel below:
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 上,我写了这个事件来做有效的改变:
On MainWindowView.xaml.cs, I wrote this event to do the effective change:
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 检查了应用程序,发现在内部更改组合框时视图会发生变化,但 ContentControl 在视觉上仍然是空的..
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..
对于我发布的代码量和我的知识缺失感到抱歉,但我在这方面工作了很长时间并且无法解决这个问题.谢谢
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
推荐答案
问题:
- 首先不要在 ViewModel (
UserControl
) 中创建与视图相关的内容.当您这样做时,这不再是 MVVM. - 从
ViewModelBase
而非ObservableObject
派生 ViewModel,除非您有充分的理由在使用 MVVMLight 时不使用ViewModelBase
.保持模型的ObservableObject
继承.作为 VM 和 M 之间的良好分离 - 接下来,您不需要像您的
_pagesViewModel
那样将所有内容都设为ObservableCollection
.你没有绑定到你的视图中的任何东西,所以这只是一种浪费.只需将其保留为私有列表或数组即可.检查一种类型与其他类似类型的实际区别. - 不确定这个,也许您将此代码片段作为演示提取,但不要使用边距来分隔网格中的项目.您的布局本质上只是 1 个 Grid 单元格,并且边距中的项目不重叠.如果您不知道该问题,请查看 WPF 布局文章.
- 在编写 UI 应用程序时,请不要忘记 OOP、封装和排序的原则.当拥有像
CurrentPageViewModel
这样的属性时,您不打算让视图切换,使属性设置器private
强制执行. - 不要过早在视图中使用代码隐藏.在这样做之前,首先检查它是否仅与视图相关的问题.我正在谈论您的
ComboBox
SelectionChanged
事件处理程序.在本演示中,您的目的是切换保存在 VM 中的绑定视图模型.因此,这不是 View 全权负责的事情.因此,寻找一种涉及 VM 的方法.
- Firstly don't ever create View related stuff in the ViewModel (
UserControl
). This is no longer MVVM when you do that. - Derive ViewModels from
ViewModelBase
and notObservableObject
unless you have a compelling reason to not useViewModelBase
when using MVVMLight. KeepObservableObject
inheritence 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
CurrentPageViewModel
which you don't intend the View to switch make the property setterprivate
to enforce that. - Don't resort to code-behind in the View too soon. Firstly check if it's only a View related concern before doing so. Am talking about your
ComboBox
SelectionChanged
event 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.
解决方案:
您可以从 此处 自己尝试一下.
You can get a working example of your code with the fixes for above from Here and try it out yourself.
点 1 -> 5 只是基本的直接更改.
Points 1 -> 5 are just basic straightforward changes.
对于 6,我在 MainViewModel 中创建了一个 SelectedVMIndex
属性,该属性绑定到 ComboBox
的 SelectedIndex
.因此,当所选索引翻转时,更新自身后的属性Setter更新 CurrentPageViewModel ,如
For 6, I've created a SelectedVMIndex
property in the MainViewModel which is bound to the SelectedIndex
of the ComboBox
. Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModel
as well such as
public int SelectedVMIndex {
get {
return _selectedVMIndex;
}
set {
if (_selectedVMIndex == value) {
return;
}
_selectedVMIndex = value;
RaisePropertyChanged(() => SelectedVMIndex);
CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
}
}
这篇关于动态用户控件更改 - WPF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!