WPF, как уничтожить и воссоздать пользовательский контроль на основе изменений в других, и проблема связывания - PullRequest
0 голосов
/ 07 ноября 2018

Я новичок в 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>

Property 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 свойство для каждого результата, а также уничтожить и перестроить коллекцию с помощью свойства-control.

РЕДАКТИРОВАТЬ 1 :

Главное окно

    <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);

            }
    }

ChildView

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>

Мой дочерний вид обновляется в отладчике, но вид не обновляется, я не уверен, что мне не хватает

1 Ответ

0 голосов
/ 07 ноября 2018

Обычно вы решаете это с помощью 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 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, у вас обычно есть некоторый базовый класс, из которого получены все модели вашего дочернего представления, но вы понимаете, что это такое.

Существуют и другие способы сделать это (например, 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>

Изначально вы увидите только две кнопки по отдельности, но нажатие на «Установить дочерний элемент» вызовет обработчик OnSetChild, который создаст новый экземпляр ChildViewModel и назначит его свойству. Благодаря DataTemplate ContentControl будет автоматически заполнен:

enter image description here

Аналогично, нажатие кнопки «Очистить дочерний элемент» очистит свойство, и желто-синий текст исчезнет. (Я использую здесь TextBlock, но, очевидно, вы можете использовать все, что захотите, включая свои собственные элементы управления).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...