wpf - WPF,如何根据其他的变化销毁和重新创建用户控件,以及绑定问题
问题描述
我是 WPF 和 C# 的新手,
我有一个由多个用户控件组成的主屏幕,其中一些需要根据选择的一个来动态销毁和重新创建。
这是我的示例 WMMW 代码,
楷模
public class Method : INotifyPropertyChanged, IDataErrorInfo
{
#region properties
private string _method;
private string _helper;
#endregion
public Method()
{
_method = "MM1";
_helper = "HM1";
}
//getter setters..
}
public class Property : INotifyPropertyChanged
{
#region Properties
private string _name;
private string _path;
private float _standarddeviation;
private string _unit;
//getter setters
}
方法视图模型
class MethodViewModel
{
#region Properties
private Method _method;
#endregion
#region Getter & Setters
public Method Method
{
get { return _method; }
}
public ICommand UpdateCommand
{
get; private set;
}
#endregion
#region Constructor
/// <summary>
/// Initialize a new interface of the MEthodViewModel class
/// </summary>
public MethodViewModel()
{
//test
_method = new Method();
UpdateCommand = new MethodUpdateCommand(this);
}
#endregion
#region Functions
public void SaveChanges()
{
//TODO: Destroy and rebuild the usercontrol
}
#endregion
}
命令
class MethodUpdateCommand : ICommand
{
private MethodViewModel _viewModel;
/// <summary>
/// Initialize a new instance of MethodNameUpdate Command
/// </summary>
/// <param name="viewModel"></param>
public MethodUpdateCommand(MethodViewModel viewModel)
{
_viewModel = viewModel;
}
#region ICOmmand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return String.IsNullOrWhiteSpace(_viewModel.Method.Error);
}
public void Execute(object parameter)
{
_viewModel.SaveChanges();
}
#endregion
}
主屏幕
<Window x:Class="WpfApplicationTest.Views.MainScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplicationTest.Views"
xmlns:control="clr-namespace:WpfApplicationTest.Controls"
mc:Ignorable="d"
Title="MainScreen" Height="573.763" Width="354.839">
<Grid Margin="0,0,0,-41">
<control:MethodControl Margin="21,23,63,460" RenderTransformOrigin="0.507,0.567"></control:MethodControl>
<control:PropertyControl Margin="0,129,0,-129"></control:PropertyControl>
</Grid>
方法控制
<UserControl x:Class="WpfApplicationTest.Controls.MethodControl"
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:WpfApplicationTest.Controls"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d" d:DesignWidth="300" Height="101.075">
<WrapPanel Orientation=" Horizontal" VerticalAlignment="Top" Height="120" >
<Label Content="Method Name:" Width="113"/>
<ComboBox Width="160" SelectedItem="{Binding Method.Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" ItemsSource="{StaticResource MethodNames}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Label Content="Reflection Type:" Width="113"/>
<ComboBox Width="160" SelectedItem="{Binding Method.Helper, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" ItemsSource="{StaticResource HelperMethods}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</WrapPanel>
属性 control.xaml
<StackPanel Name="Test"></StackPanel>
public partial class PropertyControl : UserControl
{
public PropertyControl()
{
InitializeComponent();
PopulatePropertyPanel("MM1", "HM1");
}
private void PopulatePropertyPanel(string name, string reflection)
{
//TODO: decide which mthod
//int methodindex = Constant.GetMethodNameIndex(name);
int methodindex = Array.IndexOf((String[])Application.Current.Resources["MethodNames"], name);
switch (methodindex)
{
case 0:
foreach (String prop in (String[])Application.Current.Resources["Result1"])
{
PopulateProperty(prop, true);
}
break;
default:
foreach (String prop in (String[])Application.Current.Resources["Result2"])
{
PopulateProperty(prop, false);
}
break;
}
}
private void PopulateProperty(string prop, Boolean constant)
{
Label lbl = new Label();
lbl.Content = prop;
TextBox pathtext = new TextBox();
pathtext.Text = "path";
TextBox std = new TextBox();
std.Text = "std";
TextBox unit = new TextBox();
unit.Text = "unit";
Test.Children.Add(lbl);
Test.Children.Add(pathtext);
Test.Children.Add(std);
Test.Children.Add(unit);
}
}
我想重新创建填充属性控件,每次方法控件发生更改时,我已经为它创建了一个命令。
另外,如何将属性控件中的组件与属性模型绑定,我需要有一个属性集合(每个结果一个属性,并使用属性控件销毁和重建集合。
编辑 1:
主窗口
<ContentControl Grid.Row="1" Content="{Binding ChildViewModel}" />
资源
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
主视图模型
class MethodViewModel : INotifyPropertyChanged
{
#region Properties
private Method _method;
private PropertyViewModel _childViewModel;
#endregion
#region Getter & Setters
public PropertyViewModel ChildViewModel
{
get { return this._childViewModel; }
set
{
if (this._childViewModel != value)
{
this._childViewModel = value;
OnPropertyChanged("ChildViewModel");
}
}
}
public MethodViewModel()
{
//test
_method = new Method();
_childViewModel = new PropertyViewModel();
_childViewModel.CollectProperties(_method.Name, _method.Helper);
UpdateCommand = new MethodUpdateCommand(this);
}
public void SaveChanges()
{
ChildViewModel = new PropertyViewModel(_method.Name,
_method.Helper);
}
}
子视图
class PropertyViewModel : INotifyPropertyChanged
{
private ObservableCollection<Property> _properties;
public ObservableCollection<Property> Properties
{
get { return _properties; }
//set { _properties = value; }
}
public PropertyViewModel(string method, string reflection)
{
_properties = new ObservableCollection<Property>();
CollectProperties(method, reflection);
}
属性控制.xaml
<StackPanel x:Name="Test" Grid.Row="1">
<ItemsControl ItemsSource = "{Binding ChildViewModel.Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding StdDev, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Unit, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
我的子视图在调试器中更新,但视图没有更新,我不确定我错过了什么
解决方案
通常,您可以使用 ContentControls 和 DataTemplates 解决此问题。假设您有一个 MainViewModel 并且您希望能够显示一个子视图模型,因此您首先公开一个带有属性更改通知的属性,即如下所示:
private object _MyChild;
public object MyChild
{
get { return this._MyChild; }
set
{
if (this._MyChild != value)
{
this._MyChild = value;
RaisePropertyChanged(() => this.MyChild);
}
}
}
在主窗口的 XAML 中,您创建一个内容控件并将其绑定到此属性:
<ContentControl Content="{Binding MyChild}" />
最后,在您的资源块中,您为可能分配给此属性的每个子视图模型创建一个 DataTemplate:
<DataTemplate DataType="{x:Type local:ChildViewModel}">
<local:ChildViewControl />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildViewModel2}">
<local:ChildViewControl2 />
</DataTemplate>
... etc...
通常这个控件是不可见的,但是一旦你为属性分配了一个合适的视图模型,它就会根据你在数据模板中指定的内容自动填充:
this.MyChild = new ChildViewModel(); // <-- child control gets created and appears
实际上,您的属性不会是 type object
,您通常有一些所有子视图模型都派生自的基类,但您明白了。
还有其他方法可以做到这一点(例如 DataTriggers),但 DataTemplates 是您通常用于诸如您所描述的情况的情况。
更新:这里有一些完整的工作代码,想象你的 MainViewModel 有一个子视图模型的属性和几个按钮处理程序来设置和清除子:
public class MainViewModel : ViewModelBase
{
// Child property
private ChildViewModel _Child;
public ChildViewModel Child
{
get { return this._Child; }
set
{
if (this._Child != value)
{
this._Child = value;
RaisePropertyChanged(() => this.Child);
}
}
}
// Set child
private ICommand _SetChildCommand;
public ICommand SetChildCommand => this._SetChildCommand ?? (this._SetChildCommand = new RelayCommand(OnSetChild));
private void OnSetChild()
{
this.Child = new ChildViewModel();
}
// Clear child
private ICommand _ClearChildCommand;
public ICommand ClearChildCommand => this._ClearChildCommand ?? (this._ClearChildCommand = new RelayCommand(OnClearChild));
private void OnClearChild()
{
this.Child = null;
}
}
public class ChildViewModel : ViewModelBase
{
public string Text => "I am child type 1!";
}
然后在您的 XAML 中,您需要做的就是:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<Button Content="Set Child" Command="{Binding SetChildCommand}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5" />
<Button Content="Clear Child" Command="{Binding ClearChildCommand}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5" />
<ContentControl Content="{Binding Child}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ChildViewModel}">
<TextBlock Text="{Binding Text}" Foreground="Yellow" Background="Blue" HorizontalAlignment="Left" VerticalAlignment="Center" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
最初您只会看到这两个按钮本身,但单击“Set Child”将导致OnSetChild
调用处理程序,这将创建 ChildViewModel 的新实例并将其分配给属性。由于 DataTemplate,ContentControl 将被自动填充:
同样,单击“清除子项”按钮将清除属性,黄色/蓝色文本将消失。(我在这里使用 TextBlock,但显然你可以使用任何你想要的东西,包括你自己的自定义控件)。
推荐阅读
- scala - Scala Char 自动转换为 Integer
- python - 第 51 行:TypeError:列表索引必须是整数,而不是浮点数有什么问题请帮助我
- c# - 使用来自 c# 控制台的参数打印 SSRS .rdl 报告
- javascript - Bootstrap 5 选项卡窗格无法正确使用 flexbox
- javascript - 我不断收到此类型错误:无法读取未定义的属性“地图”
- angular - 使用管道角11从字符串中删除特定字符或单词
- php - 根据子值合并多维数组的子数组
- java - Java Spring如何检查两个对象是否相等并且其中一个是LAZY加载的
- logging - 机器人框架日志关键字 - 参数“控制台”不起作用
- facebook-workplace - 作为对 Facebook Workplace 中之前帖子的回复/回复发布