WPF,如何根据其他更改销毁和重新创建用户控件,以及绑定问题
WPF, how to destroy and recreate usercontrol based on changes in other, & binding problem
我是 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
}
MethodViewModel
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);
}
}
我想重新创建填充 属性-control,每次 method-control 发生变化时,我已经为它创建了一个命令。
此外,如何将 属性 控件中的组件与 属性 模型绑定,
我需要有一个属性集合(每个结果 1 属性,并使用 属性-control.
销毁和重建集合
编辑 1:
主要window
<ContentControl Grid.Row="1" Content="{Binding ChildViewModel}" />
资源
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
MainViewModel
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 中为您的主要 window 创建一个内容控件并将其绑定到此 属性:
<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
在实践中,您的 属性 不会是 object
类型,您通常有一些基础 class 派生自所有子视图模型,但您明白了.
还有其他方法可以做到这一点(例如 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 将自动填充:
同样,单击 "Clear child" 按钮将清除 属性 并且 yellow/blue 文本将消失。 (我在这里使用的是 TextBlock,但显然您可以使用任何您想要的东西,包括您自己的自定义控件)。
我是 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
}
MethodViewModel
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);
}
}
我想重新创建填充 属性-control,每次 method-control 发生变化时,我已经为它创建了一个命令。
此外,如何将 属性 控件中的组件与 属性 模型绑定, 我需要有一个属性集合(每个结果 1 属性,并使用 属性-control.
销毁和重建集合编辑 1:
主要window
<ContentControl Grid.Row="1" Content="{Binding ChildViewModel}" />
资源
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
MainViewModel
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 中为您的主要 window 创建一个内容控件并将其绑定到此 属性:
<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
在实践中,您的 属性 不会是 object
类型,您通常有一些基础 class 派生自所有子视图模型,但您明白了.
还有其他方法可以做到这一点(例如 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 将自动填充:
同样,单击 "Clear child" 按钮将清除 属性 并且 yellow/blue 文本将消失。 (我在这里使用的是 TextBlock,但显然您可以使用任何您想要的东西,包括您自己的自定义控件)。