ViewModel绑定错误

时间:2011-01-09 06:28:22

标签: wpf mvvm mvvm-light

我遇到了错误。这是场景:

  1. wpf app加载并且ScrollViewer的内容绑定到ViewModel中名为ActiveFunction(其类型为UserControl)的属性。在该ViewModel的构造函数中为该属性设置了自定义控件(UserCtrl1)。
  2. 按下一个按钮,发出一个命令,将ActiveFunction属性设置为新的UserControl(UserCtrl2)。也就是说,this.ActiveFunction = new UserCtrl2();
  3. 新的UserControl作为ScrollViewer的内容加载。一切似乎都很好。
  4. 然后,按下一个按钮,发出一个命令,将ActiveFunction属性设置回原始UserControl(this.ActiveFunction = new UserCtrl1();)。
  5. 此时,抛出异常 - “指定元素已经是另一个元素的逻辑子元素。首先断开它。”
  6. 任何人都可以帮我解决这个问题。我很乐意上传整个VS解决方案,如果这有帮助(它不是那么大)。我真的很想了解这项技术的缺陷。我现在似乎正在与技术作斗争,而不是利用它的力量。

    干杯

3 个答案:

答案 0 :(得分:0)

我对您描述的场景进行了最简单的实现,并且对我来说没有错误。我会为它发布代码。请指出代码中的差异所在。

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

答案 1 :(得分:0)

我进一步调查,发现问题出在UserCtrl1中包含的控件上。我完全迷失了。所以,最简单的方法是让我在线发布我的VS解决方案。它比我试图解释一些我不理解的东西(对你来说也更容易)要简单得多。 Click here to download

可以非常轻松地重新创建异常。运行解决方案(VS 2010),单击“进度”按钮。然后单击“项目”按钮(返回应用程序首次启动时加载的原始用户控件)。

请注意,UserCtrl1实际上是 NewWork ,而UserCtrl2是 ProgressView

答案 2 :(得分:0)

您收到该错误的原因是您将ScrollViewer的内容设置为UserControl。通过这样做,您将UserCtrl1(UserControl)的父级设置为ScrollViewer。如果您不能将两个子项设置为ScrollViewer,那么当您尝试将UserCtrl2设置为ActiveFunction时会发生这种情况。你真正应该做的是过度利用WPF中ViewModels和DataTemplates的强大功能。

从您发布的代码我改变它以使用更多的MVVM方法。

  1. 使用ViewModels和DataTemplates。最好使用视图模型,因为它是纯代码,您不再需要使用此父/子UI关系。你只需像普通物体一样设置东西。通过为特定类指定datatemplate,您可以为您完成可视化显示。这是代码和视觉方面的完全分离。
  2. 命令。我正在使用Command来处理按钮单击。如果你想沿着MVVM路线走下去,你也应该这样做。除了帮助分离逻辑和视图之外,您还可以根据需要对命令进行单元测试。
  3. 这是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设置为viewmodel。这不是最好的方法,因为你是硬编码的东西。更好的方法是利用数据模板并让WPF处理它。

        using System.Windows;
    
    namespace LogicalChildException
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                DataContext = new MainViewModel();
            }
        }
    }
    

    以下是ViewModels的代码。我使用了从这里http://www.wpftutorial.net/DelegateCommand.html找到的DelegateCommand的想法。 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世界中更清洁,但这可以帮助您解决当前的问题。