Ошибка привязки ViewModel - PullRequest
1 голос
/ 09 января 2011

Я обнаружил ошибку. Вот сценарий:

  1. Приложение wpf загружается, и содержимое ScrollViewer привязывается к свойству в ViewModel, называемому ActiveFunction (который имеет тип UserControl). Пользовательский элемент управления (UserCtrl1) устанавливается для этого свойства в конструкторе этой ViewModel.
  2. Нажата кнопка, которая выдает команду, которая устанавливает свойство ActiveFunction для нового UserControl (UserCtrl2). То есть this.ActiveFunction = new UserCtrl2 ();
  3. Новый UserControl загружается как содержимое ScrollViewer. Все вроде нормально.
  4. Затем нажимается кнопка, которая выдает команду, которая устанавливает свойство ActiveFunction обратно на исходный UserControl (this.ActiveFunction = new UserCtrl1 ();).
  5. В этот момент выдается исключение - "Указанный элемент уже является логическим дочерним элементом другого элемента. Сначала отключите его."

Может кто-нибудь помочь мне решить эту проблему. Я рад загрузить все решение VS, если это поможет (это не так уж и много). Я просто очень хочу понять подводные камни этой технологии. Кажется, я сейчас сражаюсь против технологии, а не использую ее мощь.

Приветствия

Ответы [ 3 ]

0 голосов
/ 09 января 2011

Я исследовал дальше и обнаружил, что проблема в элементе управления, который содержится в UserCtrl1.Я полностью потерян с этим.Поэтому самое простое для меня - опубликовать решение VS для проекта в Интернете.Это будет намного проще, чем я, пытаясь объяснить что-то, чего я не понимаю (и проще для вас тоже). Нажмите здесь, чтобы загрузить .

Исключение можно воссоздать очень легко.Запустите решение (VS 2010), нажмите кнопку Progress.Затем нажмите кнопку «Проект» (которая возвращает исходный пользовательский элемент управления, который загружается при первом запуске приложения).

Обратите внимание, что UserCtrl1 на самом деле NewWork , а UserCtrl2 - ProgressView .

0 голосов
/ 09 января 2011

Причина, по которой вы получаете эту ошибку, заключается в том, что вы устанавливаете содержимое ScrollViewer как UserControl.Тем самым вы устанавливаете родительский элемент UserCtrl1 (UserControl) в ScrollViewer.Если вы не можете установить двух дочерних элементов в ScrollViewer, что и происходит, когда вы пытаетесь установить UserCtrl2 в качестве ActiveFunction.Что вы действительно должны делать, так это слишком использовать возможности ViewModels и DataTemplates в WPF.

Из кода, который вы опубликовали, я изменил его, чтобы использовать более MVVM-подход.

  1. Использование ViewModels и DataTemplates.Лучше использовать viewmodels, потому что это чистый код, вам больше не нужно возиться с этими отношениями родительского / дочернего интерфейса.Вы просто устанавливаете вещи как обычные объекты.Указав табличку данных для определенного класса, вы сделали визуальный дисплей для вас.Это полное разделение между кодом и визуальными аспектами.
  2. Команды.Я использую команду для обработки нажатия кнопки.Это то, как вы должны это делать, если хотите пойти по маршруту MVVM.Помимо помощи в разделении логики и представления, вы также можете выполнять модульные тесты для команд, также с необходимостью пользовательского интерфейса.

Вот MainWindow.

<Window x:Class="LogicalChildException.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:LogicalChildException"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <ResourceDictionary>
        <DataTemplate DataType="{x:Type local:UserControl1ViewModel}">
            <local:UserControl1 />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:UserControl2ViewModel}">
            <local:UserControl2 />
        </DataTemplate>
    </ResourceDictionary>
</Window.Resources>
<DockPanel>
    <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
        <Button Command="{Binding SwitchCommand}">Change UserControl</Button>
    </StackPanel>
    <ScrollViewer Content="{Binding ActiveFunction}">

    </ScrollViewer>
</DockPanel>

Вот код для MainWindow.xaml.cs.В основном то, что я делаю здесь, я устанавливаю DataContext этого представления в качестве модели представления.Это не лучший способ сделать это, потому что вы жестко программируете вещи.Лучше было бы использовать шаблоны данных и позволить WPF обрабатывать их.

    using System.Windows;

namespace LogicalChildException
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MainViewModel();
        }
    }
}

Вот код для моделей представления.Я использовал идею DelegateCommand, которую нашел здесь http://www.wpftutorial.net/DelegateCommand.html. UserControl1ViewModel и UserControl2ViewModel - просто фиктивные объекты, но вы можете заставить их реализовать INotifyPropertyChanged, а затем использовать его для привязки в вашей таблице данных.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Input;

namespace LogicalChildException
{
    public class DelegateCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;

        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action<object> execute,
                       Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }

    public class MainViewModel : INotifyPropertyChanged 
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ICommand _switchCommand;
        private object _activeFunction;

        private object _userControl1;
        private object _userControl2;

        public MainViewModel()
        {
            _switchCommand = new DelegateCommand(OnSwitch);

            _userControl1 = new UserControl1ViewModel();
            _userControl2 = new UserControl2ViewModel();

            ActiveFunction = _userControl1;
        }

        public ICommand SwitchCommand
        {
            get
            {
                return _switchCommand;
            }
        }

        public object ActiveFunction
        {
            get
            {
                return _activeFunction;
            }
            set
            {
                if (_activeFunction != value)
                {
                    _activeFunction = value;
                    OnPropertyChanged("ActiveFunction");
                }
            }
        }            

        private void OnSwitch(object obj)
        {
            // do logic for switching "usercontrols" here
            if (ActiveFunction == null)
            {
                // if null, just set it to control 1
                ActiveFunction = _userControl1;
            }
            else
            {
                ActiveFunction = (ActiveFunction is UserControl1ViewModel) ? _userControl2 : _userControl1;
            }

        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class UserControl1ViewModel
    {
    }

    public class UserControl2ViewModel
    {
    }
}

Здесь есть много областей, которые вы могли бы улучшить, чтобы сделать чище в мире MVVM, но это должно помочь вам решить вашу проблему, которая у вас есть в настоящее время.

0 голосов
/ 09 января 2011

Я сделал самую простую реализацию описанного вами сценария, и он работает без ошибок для меня.Я опубликую код для этого.Пожалуйста, укажите, где различия в вашем коде.

<Window x:Class="LogicalChildException.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
            <Button Name="ChangeUserControl" Click="ChangeUserControl_Click">Change UserControl</Button>
        </StackPanel>
        <ScrollViewer Content="{Binding ActiveFunction}">

        </ScrollViewer>
    </DockPanel>
</Window>

<UserControl x:Class="LogicalChildException.UserControl1"
             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="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Height="500" FontSize="30">
            UserControl One
        </TextBlock> 
    </Grid>
</UserControl>

<UserControl x:Class="LogicalChildException.UserControl2"
             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="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Height="300" FontSize="10">
            UserControl Two
        </TextBlock>

    </Grid>
</UserControl>

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace LogicalChildException
{

    public partial class MainWindow : Window,INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            ActiveFunction = new UserControl1();
            DataContext = this;
        }

        private void ChangeUserControl_Click(object sender, RoutedEventArgs e)
        {
            if (ActiveFunction is UserControl1)
                ActiveFunction = new UserControl2();
            else
                ActiveFunction = new UserControl1();
        }

        private UserControl _activeFunction;
        public UserControl ActiveFunction
        {
            get { return _activeFunction; }
            set
            {
                _activeFunction = value;
                if(PropertyChanged!=null)
                    PropertyChanged(this,new PropertyChangedEventArgs("ActiveFunction"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
...