首页 > 解决方案 > 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>

我的子视图在调试器中更新,但视图没有更新,我不确定我错过了什么

标签: wpfdynamicdata-bindinguser-controls

解决方案


通常,您可以使用 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,但显然你可以使用任何你想要的东西,包括你自己的自定义控件)。


推荐阅读