为什么复制的UIElement或FrameworkElement会失去样式?

7
我正在尝试加载带有StyleDataTriggerUIElementFrameworkElementGrid。以下是一个简单的本机XAML,它按预期工作;通过三种状态切换复选框,可为矩形提供三种不同的样式(请参见底部图像)。
<Grid
    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:XamlVsLoad"
    mc:Ignorable="d"
    Height="450" Width="800">
    <CheckBox Name="MyCheck" IsChecked="True" Content="Checkbox" IsThreeState="True" Margin="10,10,0,0" Width="200"/>
    <Rectangle Height="100" Width="100" StrokeThickness="5" Margin="10,50,100,100">
        <Rectangle.Style>
            <Style TargetType="Rectangle">
                <Setter Property="Fill" Value="White"/>
                <Setter Property="Stroke" Value="Black"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ElementName=MyCheck, Path=IsChecked}" Value="True" >
                        <Setter Property="Fill" Value="Orange"/>
                        <Setter Property="Stroke" Value="Blue"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding ElementName=MyCheck, Path=IsChecked}" Value="False" >
                        <Setter Property="Fill" Value="Blue"/>
                        <Setter Property="Stroke" Value="Orange"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Rectangle.Style>
    </Rectangle>
</Grid>

如果我将相同的文本保存到文件中,并像这样在XAML中动态加载到WindowViewboxs中,它也可以正常工作:

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="100"/>
    <ColumnDefinition Width="100"/>
    <ColumnDefinition Width="100"/>
    <ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
    <DockPanel>
        <Label Content="Native" DockPanel.Dock="Top" HorizontalAlignment="Center"  Margin="10,10,10,10" />
        <Viewbox Name="NativeViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
           <!-- Native Grid Omitted for brevity 3 uniquely named checkboxes -->
        </Viewbox>
</DockPanel>
<DockPanel Grid.Column="1">
    <DockPanel>
        <Label Content="Dynamic" DockPanel.Dock="Top"  HorizontalAlignment="Center"  Margin="10,10,10,10" />
        <Viewbox Name="DynamicViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
    </DockPanel>
</DockPanel>
<DockPanel Grid.Column="2">
    <DockPanel>
        <Label Content="Clone" DockPanel.Dock="Top" HorizontalAlignment="Center"  Margin="10,10,10,10" />
        <Viewbox Name="CloneViewbox" Height="auto" Width="200" DockPanel.Dock="Top" />
    </DockPanel>
</DockPanel>
<DockPanel Grid.Column="3">
    <DockPanel>
        <Label Content="Saved" DockPanel.Dock="Top" HorizontalAlignment="Center"  Margin="10,10,10,10" />
        <Viewbox Name="SavedViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
    </DockPanel>
</DockPanel>

然而,如果我试图从一个Grid/容器中复制/克隆/深度复制 FrameworkElements/UIElements 到新的一个中,样式就不再起作用了。以下是我目前正在加载和克隆它们的各种方法:

public partial class ReadCopy : Window {
    public ReadCopy() {
        InitializeComponent();
        // test the dynamic load
        Grid NativeXaml = ReadGrid("C:\\XamlFiles\\LoadXaml.xaml");
        DynamicViewbox.Child = NativeXaml; // honors style

        // test the Clone load
        Grid CloneXaml = new Grid();
        foreach (FrameworkElement fe in NativeXaml.Children) CloneXaml.Children.Add(Clone(fe));
        CloneViewbox.Child = CloneXaml;    // doesn't honor style

        // test the save Clone and then load
        StringBuilder outstr = new StringBuilder();
        XmlWriterSettings settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true, IndentChars = "  ", NewLineChars = "\r\n", NewLineHandling = NewLineHandling.Replace };
        XamlDesignerSerializationManager dsm = new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings)) { XamlWriterMode = XamlWriterMode.Expression };
        XamlWriter.Save(CloneXaml, dsm);
        File.WriteAllText("C:\\XamlFiles\\SavedXaml.xaml", outstr.ToString());

        Grid SavedXaml = ReadGrid("C:\\XamlFiles\\SavedXaml.xaml");
        SavedViewbox.Child = SavedXaml;    // honors style and triggers again...
    }

    public Grid ReadGrid(string fn) {
        FileStream fs = new FileStream(fn, FileMode.Open);
        return XamlReader.Load(fs) as Grid;
    }

    public FrameworkElement Clone(FrameworkElement it) {
        FrameworkElement clone;
        using (var stream = new MemoryStream())  {
            XamlWriter.Save(it, stream);
            stream.Seek(0, SeekOrigin.Begin);
            clone = (FrameworkElement)XamlReader.Load(stream);
        }
        clone.Style = it.Style; // setting it or not has no effect
        return clone;
    }
}

通过XamlWriter.Save克隆XAML的起始简单示例网格的输出:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CheckBox IsChecked="True" IsThreeState="True" Style="{x:Null}" Name="MyCheck" Width="200" Margin="10,10,0,0">Checkbox</CheckBox>
  <Rectangle StrokeThickness="5" Width="100" Height="100" Margin="10,50,100,100">
    <Rectangle.Style>
      <Style TargetType="Rectangle">
        <Style.Triggers>
          <DataTrigger Binding="{Binding Path=IsChecked, ElementName=MyCheck}" Value="True">
            <Setter Property="Shape.Fill">
              <Setter.Value>
                <SolidColorBrush>#FFFFA500</SolidColorBrush>
              </Setter.Value>
            </Setter>
            <Setter Property="Shape.Stroke">
              <Setter.Value>
                <SolidColorBrush>#FF0000FF</SolidColorBrush>
              </Setter.Value>
            </Setter>
          </DataTrigger>
          <DataTrigger Binding="{Binding Path=IsChecked, ElementName=MyCheck}" Value="False">
            <Setter Property="Shape.Fill">
              <Setter.Value>
                <SolidColorBrush>#FF0000FF</SolidColorBrush>
              </Setter.Value>
            </Setter>
            <Setter Property="Shape.Stroke">
              <Setter.Value>
                <SolidColorBrush>#FFFFA500</SolidColorBrush>
              </Setter.Value>
            </Setter>
          </DataTrigger>
        </Style.Triggers>
        <Style.Resources>
          <ResourceDictionary />
        </Style.Resources>
        <Setter Property="Shape.Fill">
          <Setter.Value>
            <SolidColorBrush>#FFFFFFFF</SolidColorBrush>
          </Setter.Value>
        </Setter>
        <Setter Property="Shape.Stroke">
          <Setter.Value>
            <SolidColorBrush>#FF000000</SolidColorBrush>
          </Setter.Value>
        </Setter>
      </Style>
    </Rectangle.Style>
  </Rectangle>
</Grid>

虽然我可以看到它改变了Style,但是当我使用克隆设置Viewbox.Child时,我不明白为什么它不再起作用。更加令人困惑的是,加载保存的克隆文件之后,它就能正常工作了。下面是四种情况(本地、动态、克隆、保存的克隆重新加载)的效果:

4 Examples

有人能解释如何正确地通过复制/克隆来保留样式吗?


2
提示:查看XamlWriter.Save的输出(将其写入临时文件或其他地方)。 - Dave M
我已经颁发了悬赏,但实际上并没有得到答案。如果有其他人有更明确的答案,我会尝试再次颁发悬赏给他们。 - codebender
1个回答

5

Rectangle上的Style是由CheckBox触发的。因此,Clone()在分别加载子元素时无法正常工作。为了同时加载它们,需要克隆它们的父级元素,即Grid本身。我重现了你的问题,并且这个方法对我起作用:

  Grid NativeXaml = ReadGrid();
  DynamicViewbox.Child = NativeXaml;

  Grid CloneXaml = (Grid)Clone(NativeXaml);
  CloneViewbox.Child = CloneXaml;

有趣的观察。然而,我需要能够从一个容器(网格、画布……)克隆/复制项目到另一个容器。这个答案没有解释为什么一旦这些项目被单独复制、保存和重新加载,它们就可以工作。 - codebender
我看到很多人谈论XAMLReader.Load()有时会破坏绑定,但没有明确的文档。如果您正在尝试构建一个可重用的库,其中两个项目像这样绑定在一起,您可以尝试将它们合并到一个UserControl中,并将UserControl父级克隆到最终容器中。 - T K
@codebender,您并没有真正保存和重新加载“单个”项目。如果您查看您的代码XamlWriter.Save(CloneXaml, dsm);,您实际上是保存整个“Grid”,而不是单个项目。 - Suresh
@sthotakura 每个项目都会被单独克隆到新的网格中,然后保存。不知何故,样式存在,但在完全重新加载之前不被尊重/识别。将每个可能附有样式的项目放置到我的网格/容器上的另一个容器中并不是一个真正的选择。 - codebender

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接