【问题标题】:How to use ContentTemplate to avoid "Specified element is already the logical child of another element. Disconnect it first" error如何使用 ContentTemplate 避免“指定元素已经是另一个元素的逻辑子元素。先断开它”错误
【发布时间】:2012-06-28 16:07:02
【问题描述】:

我知道这个问题已经被问过无数次了,但我不明白他们的问题是什么或如何效仿他们的例子。 我确实找到了她的名字 Rachel,发布了一篇关于它的博客,但她的解释太简短了..

http://rachel53461.wordpress.com/2011/07/17/wpf-error-specified-element-is-already-the-logical-child-of-another-element-disconnect-it-first/

这是我在尝试遵循此处示例之前所拥有的:

<Window x:Class="Graph.View.MainView.Main"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:lc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
    Title="Main" Height="350" Width="525" 

    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking">

<DockPanel LastChildFill="True">
       <DockPanel>
        <Label  Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
        </DockPanel>
    </DockPanel>

因为ScreenContent 有一个逻辑父级,所以我不能重用它。在尝试效仿 Rachel 的榜样后:

<Window x:Class="Graph.View.MainView.Main"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
        Title="Main" Height="350" Width="525" 
       >
    <Window.Resources>
        <Style     TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate"> 
                <Setter.Value>
                    <DataTemplate>
                        <DockPanel>
                            <Label   Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
                        </DockPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <DockPanel LastChildFill="True">            
        <Label Style="{StaticResource MyCustomContentControl}"/>
    </DockPanel>
</Window>

我该如何解决?它根本没有显示任何东西...... 谢谢。

【问题讨论】:

  • 如果你想用那个键将它用作静态资源,你至少应该设置&lt;Style x:Key="MyCustomContentControl" ...&gt;

标签: c# .net wpf devexpress


【解决方案1】:

请回答我ScreenContent 属性是什么...是字符串吗?还是一些 GUI 元素?

如果是字符串,则按照下面的解决方案...

还有一些你必须了解的概念...

  1. LabelLabel 是一个糟糕的 UI 设计。
  2. ContentControls 例如LabelButton 等。当我们想要设置它们的ControlTemplateDataTemplate 时,请遵循特定的XAML 模式。
  3. 在您的情况下,您希望分配基于数据上下文的属性ScreenContent。所以DataTemplate的方式是正确的。

但是,这不会流向您的 DataTemplate 内的 Label,因为外部 Label(您已将 Style 应用到)没有设置在其自己的 Content 属性上。

所以基本上ContentControl.ContentTemplate 仅在ContentControl 上设置了非空Content 并且在DataTemplate (ContentTemplate) 内部Content 用作@ 987654342@ 为所有项目!

注意下面的{Binding} 表达式...这是不言自明的!!

    <DockPanel LastChildFill="True">
        <DockPanel.Resources>
            <Style x:Key="MyCustomContentControl"
                   TargetType="{x:Type ContentControl}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <DockPanel>
                                    <ContentPresenter
                                             Content="{Binding}"
                                             HorizontalAlignment="Stretch"
                                             VerticalAlignment="Stretch"/>
                                </DockPanel>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </DockPanel.Resources>
        <Label Style="{StaticResource MyCustomContentControl}"
               Content="{Binding ScreenContent}" 
               HorizontalContentAlignment="Stretch"
               VerticalContentAlignment="Stretch">
        </Label>
    </DockPanel>

如果是 UserControl,请按照以下解决方案...

您是否只分配一次(给定的Label)?还是您试图将其分配为各种元素的内容?

如果它只分配一次,那么上面的解决方案应该可以工作。

但如果您打算将其分配为多个目标元素的Content,那么您需要遵循Model / ViewModel 方法。

  1. 您的所有 GUI 元素都应托管在 XAML 中。
  2. 代码隐藏不应处理 GUI 元素。
  3. 代码隐藏仅适用于 GUI 元素的 副本,即特定于 GUI (UserControl) 表示的字段的数据。

例如

示例 1...

假设您有一个 TitleControl,它就像一个粗体斜体 WPF Label,显示标题的文本。您的 DataContext 拥有这样的 TitleControl 对象,其中设置了一些标题文本。

然后在ViewModel 方法中,您应该创建一个TitleControlViewModel 的实例作为DataContext 的一部分...类似这样...

YourDataContext.MyTitleControlViewModel = new TitleControlViewModel();

TitleControlViewModel 类中,我们应该有一个名为MyTitle 的字符串类型的公共属性。

YourDataContext.MyTitleControlViewModel.MyTitle = "My Title";

现在您的模板特定 XAML 应该如下所示...

    <DataTemplate x:Key="MyTitleControlDataTemplate">
         <local:TitleControl Title="{Binding MyTitle}"/>
    </DataTemplate>

您的主机面板应如下所示...

    <ContentControl ToolTip="Title is shown here..." 
                    Content="{Binding MyTitleControlViewModel}"
                    Contenttemplate="{StatiocResource MyTitleControlDataTemplate}"/>

    <ContentControl ToolTip="Same title control is shown here also !!!"
                    Content="{Binding MyTitleControlViewModel}"
                    Contenttemplate="{StatiocResource MyTitleControlDataTemplate}"/>

因此,这样相同的标题控件似乎托管在上面的两个内容控件上。但如果你真的这么想,TitleControl 有两个不同的实例,它们只是代表同一个 MyTitleControlViewModel,因此看起来像同一个控件!

Google 了解数据模板和 MVVM 如何组合在一起。

【讨论】:

  • 那不起作用:( ScreenContent 是一个 UserControl。我将它分配给我认为可以执行的 Label 内容,但我当然不能再重复使用该 UserControl(screen)。我认为这种方法会奏效!
  • 是的,但两种情况都不同。但是,假设您有一个选项卡式用户控件。在主选项卡中,您有一个 EmployeeUserControl 实例,只要您在第二个选项卡中没有另一个 EmployeeUserControl 实例就可以了,实际上它不会让您创建一个新的 EmployeeUserControl 来充当第二个选项卡。我想我知道问题是什么,但不知道如何解决它。我基本上将制作屏幕的部分代码复制到了 SubScreenView 中。我删除了绑定,因为它会显示静态数据。但我一时兴起决定重用我拥有的 ViewModel。
  • 当我使用 ViewModel 时,它抛出了同样的异常,即使 SubScreenView 没有任何逻辑父级。所以这里的ViewModel就是原因,为什么呢?我不知道。
  • 我认为你做错了什么!提供ViewModelContentControlsDataTemplate 的原因完全相同,以便在多个实例中显示类似的控件。你能提供你的源代码吗?
  • @LewsTherin 你已经用 devexpress 标记了这个问题!你在使用任何 devexpress 控件吗?怎么样?
【解决方案2】:

如果您想重用ScreenContent(尽管它已经是某个其他元素的子元素),那么您必须先克隆它并使用克隆的控件。

您可以通过首先使用XamlWriter 对其进行序列化来克隆一个控件,然后通过使用XamlReader 对其进行反序列化来创建一个新控件,例如this -

string screenContentXml = XamlWriter.Save(ScreenContent );

//Load it into a new object:
StringReader stringReader = new StringReader(screenContentXml );
XmlReader xmlReader = XmlReader.Create(stringReader);
UIElement screenContentClone = (UIElement)XamlReader.Load(xmlReader);

在你的窗口中使用这个screenContentClone

但是,您可能会发现自己在应用变通方法来使其正常工作,因为使用 XamlWriter.Save(如绑定)存在一些限制 - Serialization Limitations of XamlWriter.Save

这里有一些其他的序列化方法 -

An XAML Serializer Preserving Bindings

XamlWriter and Bindings Serialization

虽然你的设计看起来不像 AngelWPF 提到的那样正确,你应该尝试重新设计你的窗口,你可能不需要做所有这些。

【讨论】:

  • 更容易的是使用另一个用户控件。我希望重用视图,这样我就可以随时制作小屏幕。这个限制很糟糕。无论如何,谢谢。
  • 我认为这里没有任何限制!您必须以正确的方式使用这些功能,它们应该如何使用,否则您将始终面临限制。如果您只想多次使用视图,则直接将其添加到 XAML(&lt;DockPanel&gt; &lt;local:ScreenContent /&gt; &lt;/DockPanel&gt;; 但如果您想多次使用同一个视图实例,则必须先克隆它。
【解决方案3】:

UserControls(或任何其他类型的 UI 控件)不属于 ViewModel

最好有一个您要绑定的对象,并使用DataTemplate 告诉 WPF 如何使用您的UserControl 绘制该对象

例如,如果你有

ScreenModel ScreenContent { get; set; }

ScreenModel 是一个自定义类,看起来像这样

public class ScreenModel
{
    public string Name { get; set; }
    ...
}

然后你会使用这样的东西绑定到它:

<DockPanel LastChildFill="True">
    <DockPanel>
        <ContentControl Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
    </DockPanel>
</DockPanel>

并使用DataTemplate 告诉WPF 如何绘制ScreenModel

<DataTemplate DataType="{x:Type models:ScreenModel}">
    <views:ScreenContentUserControl />
</DataTemplate>

<DataTemplate DataType="{x:Type models:ScreenModel}">
    <Label Content="{Binding Name}" />
</DataTemplate>

因为您使用的是Template,所以 WPF 将在需要时创建UserControl 的新副本,并且不会尝试在多个位置使用相同的UserControl

发生错误是因为您通过Content="{Binding ScreenContent}" 绑定多次将相同的UserControl (ScreenContent) 添加到VisualTree

即使执行诸如切换选项卡之类的操作也可能会导致此错误,因为您将通过切换离开选项卡来卸载所有 UI 对象,然后通过切换回来加载新的 UI 对象,但是您的 ScreenContent 用户控件已经拥有它的父级设置为不再存在的旧对象。

【讨论】:

  • 这看起来可能是一个很好的解决方案。但是你能进一步解释一下吗,因为我什么都没有。如果我有一个有 3 个标签的用户控件。我将如何在 xaml 中创建该用户控件,但能够用数据填写这些标签?
  • 或者它会知道如何自动执行它?我会尝试看看会发生什么。什么是模型:?
  • @LewsTherin 我添加了第二个DataTemplate 示例,它使用Label 而不是自定义UserControlScreenModel 对象是您创建的自定义类,在我的示例中,它有一个名为 Name 的属性。每当 WPF 在VisualTree(在本例中为ContentControl.Content)中遇到ScreenModel 类型的对象时,它会使用为其定义的DataTemplate 来绘制它。
  • 好吧,你说在viewmodel中创建ui控件是错误的。例如,在 ViewModel 中创建文本框并且您无法避免的情况如何。因为使用我使用的第三方库,我可以绑定到元素数组。而这些元素需要我在 VM 中创建 ui 控件……那我该怎么办?
  • @LewsTherin 没错,ViewModel 不应包含对任何 UI 控件的任何引用。如果您想要一个TextBox,您应该在您的ViewModel 上创建一个包含TextBox 文本的string 属性,然后从您的视图(XAML)中绑定您的TextBoxText 属性给它。 &lt;TextBox Text="{Binding SomeStringProperty}" /&gt;
猜你喜欢
  • 2012-11-17
  • 2012-02-20
  • 2012-08-23
  • 2011-04-30
  • 1970-01-01
  • 2023-04-05
  • 2019-05-01
  • 1970-01-01
相关资源
最近更新 更多