ViewModel 绑定错误

发布于 2024-10-11 05:38:35 字数 605 浏览 2 评论 0原文

我遇到错误。场景如下:

  1. wpf 应用程序加载,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. 此时,会引发异常 - “指定元素已经是另一个元素的逻辑子元素。请先断开它。”

任何人都可以帮我解决此问题。如果有帮助的话我很乐意上传整个 VS 解决方案(它不是那么大)。我真的很想了解这项技术的陷阱。我现在似乎是在与技术作斗争,而不是利用它的力量。

干杯

I am experiencing an error. Here is the scenario:

  1. wpf app loads and the content of a ScrollViewer is bound to a property in a ViewModel called ActiveFunction (which has a type UserControl). A custom control (UserCtrl1) is set for that property in the constructor of that ViewModel.
  2. A button is pressed which issues a command that sets the ActiveFunction property to a new UserControl (UserCtrl2). That is, this.ActiveFunction = new UserCtrl2();
  3. The new UserControl is loaded as the content of the ScrollViewer. Everything seems fine.
  4. Then, a button is pressed which issues a command that sets the ActiveFunction property back to the original UserControl (this.ActiveFunction = new UserCtrl1();).
  5. At this point, an exception is thrown - "Specified element is already the logical child of another element. Disconnect it first."

Can anyone help me resolve this issue. I'm happy to upload the whole VS solution if that helps (its not that big). I just really want to get an understanding of the pitfalls of this technology. I seem to be fighting against the technology right now, rather than harnessing its power.

Cheers

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

琉璃梦幻 2024-10-18 05:38:35

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

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

I made the most simple implementation of the scenario you described and it works without errors for me. I will post the code for it. Please point out where the differences are in your code.

<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;
    }
}
就此别过 2024-10-18 05:38:35

我进一步调查后发现问题出在 UserCtrl1 中包含的控件上。我对此完全迷失了。所以,对我来说最简单的事情就是在线发布我的项目的 VS 解决方案。这比我试图解释一些我不明白的事情要简单得多(对你来说也更容易)。 点击此处下载

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

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

I've investigated further and found that the problem is with a control which is contained in UserCtrl1. I'm completely lost with this. So, the easiest thing is for me to post my VS solution for the project online. It will be much simpler than me trying to explain something I don't understand (and easier for you too). Click here to download.

The exception can be recreated very easily. Run the solution (VS 2010), click the Progress button. Then click the Project button (which returns to the original user control that is loaded when the app first starts).

Note that UserCtrl1 is actually NewWork and UserCtrl2 is ProgressView.

望喜 2024-10-18 05:38:35

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

根据您发布的代码,我将其更改为使用更多 MVVM 方法。

  1. 使用视图模型和数据模板。最好使用视图模型,因为它是纯代码,您不再需要搞乱这种父/子 UI 关系。您只需像设置普通对象一样设置即可。通过为某个类指定数据模板,您就可以完成可视化显示。这是代码和视觉方面的完全分离。
  2. 命令。我正在使用命令来处理按钮单击。如果您想走 MVVM 路线,您也应该这样做。除了帮助分离逻辑和视图之外,您还可以在需要 UI 的情况下对命令进行单元测试。

这是主窗口。

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

这是 ViewModel 的代码。我使用了从这里找到的 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 世界变得更干净,但这应该可以帮助您解决当前遇到的问题。

The reason you are getting that error is that you are setting the content of the ScrollViewer to be a UserControl. By doing so you are setting the parent of UserCtrl1 (UserControl) to be the ScrollViewer. If you can't set two children to a ScrollViewer which is what is happening when you try to set UserCtrl2 as ActiveFunction. What you should really be doing is too leverage the power of ViewModels and DataTemplates within WPF.

From the code you have posted I changed it to use a more MVVM approach.

  1. Using ViewModels and DataTemplates. It is better to use viewmodels because it is pure code you don't need to mess around with this parent/child UI relationship anymore. You just set things as you would normal objects. By specifying the datatemplate for a certain class, you have the visual display done for you. This is the whole separation between code and visual aspects.
  2. Commands. I am using a Command to handle the button click. This is how you should be doing it too if you want to go down the MVVM route. On top of just help separating logic and view, you can also do unit tests against the commands too with the need of a UI.

Here is the 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>

Here is the code behind for the MainWindow.xaml.cs. Basically what I am doing here is I am setting the DataContext of this view to be the viewmodel. This is not the best way to do it because you are hardcoding things. Better way would be to leverage data templates and let WPF handle it.

    using System.Windows;

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

            DataContext = new MainViewModel();
        }
    }
}

Here is the code for the ViewModels. I have used the idea of a DelegateCommand which I found from here http://www.wpftutorial.net/DelegateCommand.html. UserControl1ViewModel and UserControl2ViewModel are just dummy objects but you could make them implement INotifyPropertyChanged and then use that for binding in your datatemplate.

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

There are many areas here that you could improve upon to make cleaner in the MVVM world but this should help you resolve your issue that you are currently having.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文