【问题标题】:Make Copy of xaml child elements制作 xaml 子元素的副本
【发布时间】:2015-10-01 18:06:42
【问题描述】:

我正在开发一个 Windows Phone 应用程序。我想将一个画布的孩子复制到另一个画布。我可以用下面的代码做到这一点,但问题是我必须先从一个画布上删除它。代码是:

private void add_template_Click(object sender, RoutedEventArgs e)
{
    var childrenList = Template_canvas1.Children.Cast<UIElement>().ToArray();
    root.Children.Clear();
    foreach (var c in childrenList)
    {
        Template_canvas1.Children.Remove(c);
        root.Children.Add(c);
    }
}

我想将这些元素保留在画布上。还有其他方法吗?

【问题讨论】:

    标签: c# wpf xaml canvas


    【解决方案1】:

    与其尝试将相同的Template_canvas1.Children 添加到root 画布,不如先复制这些Children,然后将副本添加到root 画布。

    public static T CloneXaml<T>(T source)
    {
        string xaml = XamlWriter.Save(source);
        StringReader sr = new StringReader(xaml);
        XmlReader xr = XmlReader.Create(sr);
        return (T)XamlReader.Load(xr);
    }
    

    然后将循环更改为:

    foreach (var c in childrenList)
    {
        var copy = CloneXaml(c);
        root.Children.Add(copy);
    }
    

    我没有测试过这段代码,所以你可能需要稍微修改一下,但它应该能让你朝着正确的方向前进。

    或者,您也可以使用以下从Dr Herbie's answer 复制的代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Windows.UI.Xaml;
    using System.Reflection;
    using Windows.UI.Xaml.Controls;
    
    namespace UIElementClone {
      public static class UIElementExtensions {
        public static T DeepClone<T>(this T source) where T : UIElement {
          T result; // Get the type 
          Type type = source.GetType(); // Create an instance 
          result = Activator.CreateInstance(type) as T;
          CopyProperties<T>(source, result, type);
          DeepCopyChildren<T>(source, result);
          return result;
        }
    
        private static void DeepCopyChildren<T>(T source, T result) where T : UIElement {
          // Deep copy children. 
          Panel sourcePanel = source as Panel;
          if (sourcePanel != null) {
            Panel resultPanel = result as Panel;
            if (resultPanel != null) {
              foreach (UIElement child in sourcePanel.Children) {
                // RECURSION! 
                UIElement childClone = DeepClone(child);
                resultPanel.Children.Add(childClone);
              }
            }
          }
        }
    
        private static void CopyProperties<T>(T source, T result, Type type) where T : UIElement {
          // Copy all properties. 
          IEnumerable<PropertyInfo> properties = type.GetRuntimeProperties();
          foreach (var property in properties) {
            if (property.Name != "Name") { // do not copy names or we cannot add the clone to the same parent as the original. 
              if ((property.CanWrite) && (property.CanRead)) {
                object sourceProperty = property.GetValue(source);
                UIElement element = sourceProperty as UIElement;
                if (element != null) {
                  UIElement propertyClone = element.DeepClone();
                  property.SetValue(result, propertyClone);
                }
                else {
                  try {
                    property.SetValue(result, sourceProperty);
                  }
                  catch (Exception ex) {
                    System.Diagnostics.Debug.WriteLine(ex);
                  }
                }
              }
            }
          }
        }
      }
    } 
    

    如果这些都不适合您,恐怕您必须实现自己的序列化程序。看起来David Poll 实现了一个不错的 serlizer,所以看看。使用他的serlizer就像使用XamlWriter一样简单,然后你可以使用XamlReader

    public static T CloneXaml<T>(T source)
    {
        UiXamlSerializer uxs = new UiXamlSerializer();
        string xaml = uxs.Serialize(source);
    
        StringReader sr = new StringReader(xaml);
        XmlReader xr = XmlReader.Create(sr);
        return (T)XamlReader.Load(xr);
    }
    

    要获得此功能,请下载他的Slab library,转到“Binaries”文件夹并将所有以“SLaB.Utilities.Xaml.Serializer”开头的 dll 复制到您的项目中。可能需要一些其他 dll 作为依赖项。如果您想查看代码并学习,他在库中有示例解决方案。

    【讨论】:

    • 如何复制这些孩子?
    • 检查this post,它有一个Xaml serlializer 的实现,可以为你解决问题。
    • 查看我的更新答案。我添加了一个进行克隆的类。
    • Windows phone sdk 不支持 Windows.UI.Xaml
    • 好的,但是您检查过我在之前评论中提到的this post 吗?它应该可以在 Windows Phone 上运行。如果您愿意,我可以将其添加到答案中。
    【解决方案2】:

    如果没有一个好的Minimal, Complete, and Verifiable example 清楚地显示您尝试过的内容,并准确描述您想要达到的目标,就不可能确定最佳答案是什么。

    也就是说,鉴于 WPF 在某种意义上已经知道如何“克隆”元素,通过使用数据模板,你的问题听起来很像 XY Problem 对我来说。也就是说,你只认为你需要从字面上克隆你的可视化树中的元素,而实际上你应该做的是定义一个表示数据的视图模型要为要“克隆”的元素显示,定义单个数据模板,该模板使用 XAML 来描述将在视图模型中显示数据的可视元素,然后只需在需要的任何位置应用模板即可要“克隆”的视觉元素。

    即他们不会真的被克隆。相反,WPF 将完全按照您的要求自动填充一个全新的可视元素子树。由于模板允许您完全定义所有方面,因此不存在与例如相关的问题。尝试连接事件订阅、正确设置绑定等。

    在您的具体示例中(尽管它很模糊),听起来您很可能想要使用ItemsControl 元素,其中ItemsPanelCanvas 对象。然后,您将定义一个DataTemplate,它表示ItemsPanel 中的一个单个 项;该模板将通过设置其DataType 属性或通过设置ItemsControl.ItemTemplate 属性显式引用。然后,当您想要数据的视觉对象副本时,您只需创建一个ItemsControl,而不是克隆任何东西。

    【讨论】:

      【解决方案3】:

      用户反馈它不适用于 Windows Phone 后的新答案

      可以下载完整的最终Windows Phone App here

      存在一些 API 差异,例如,我们必须使用 pinfo.GetSetMethod() 等代替 pinfo.SetMethod 属性。

      其次,我在不知不觉中没有检查不能复制的 Name 属性,否则我们将创建另一个具有相同名称的实例。

      第三,我发布了简单控件的简单案例,例如 ButtonTextBoxRectangle 等不包含子项的控件。如果是这种情况,您也必须使用递归深度克隆来克隆孩子。因为孩子可以有更多的孩子等等。

      foreach (UIElement oldElem in Canvas1.Children)
      {
          try
          {               
              Type t = oldElem.GetType();
              UIElement newElem = (UIElement)Activator.CreateInstance(t);
      
              PropertyInfo[] info = t.GetProperties();
              int i = 0;
              foreach (PropertyInfo pinfo in info)
              {
                  if (pinfo.Name == "Name") continue;
                  try
                  {
                      if (pinfo.GetSetMethod() != null) // avoid read-only properties
                          pinfo.SetValue(newElem, pinfo.GetValue(oldElem, null),null);
                  }
                  catch (Exception ex)
                  {
                      Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
                  }
              }
      
              Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
              Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
      
              Canvas2.Children.Add(newElem);
          }        
          catch (Exception ex)
          {         
          }
      }
      

      如果您要进行真正的深度克隆,请将上面外部 try 块中的代码替换为更简单的代码:

          foreach (UIElement oldElem in Canvas1.Children)
          {
              try
              {
                  UIElement newElem = oldElem.DeepClone();
                  Canvas2.Children.Add(newElem);
      
                  Canvas.SetLeft(newElem, Canvas.GetLeft(oldElem));
                  Canvas.SetTop(newElem, Canvas.GetTop(oldElem));                    
              }                
              catch (Exception ex){ }
          }
      

      仅基于 WPF 的旧答案

      不知道 windows phone 但在 WPF 中这会创建一个新元素并将其放在另一个画布中完全相同的位置。检查它是否符合您的需求,否则我会再次更新。

      foreach (UIElement oldElem in Canvas1.Children)
      {
          Type t = oldElem.GetType();
          UIElement newElem = (UIElement)Activator.CreateInstance(t);
          PropertyInfo[] info = t.GetProperties();
          int i = 0;
          foreach (PropertyInfo pinfo in info)
          {
              try
              {
                  if (pinfo.SetMethod != null) // avoid read-only properties
                      pinfo.SetValue(newElem, pinfo.GetValue(oldElem));                        
              }
              catch (Exception ex)
              {
                  Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
              }
          }
      
          Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
          Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
          Canvas.SetRight(newElem, Canvas.GetRight((oldElem)));
          Canvas.SetBottom(newElem, Canvas.GetBottom((oldElem)));
      
          Canvas2.Children.Add(newElem);
      }
      

      【讨论】:

      • 它会抛出一个错误“元素已经是另一个父元素的成员”。这是我之前遇到的同样的错误。可能是因为 xaml 孩子不能是超过 1 个父母的孩子
      • 我在发布答案之前已经检查过了。我不知道你在做什么
      • 创建2个画布,将一些控件放在canvas1中,上面的代码将它们复制并放置在另一个画布中完全相同的位置。
      • 您的答案适用于 xaml 应用程序,但不适用于 windows phone。它在以下行引发错误“System.Reflection.TargetInvocationException”:pinfo.SetValue(newElem, pinfo.GetValue(oldElem));
      猜你喜欢
      • 2011-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-31
      相关资源
      最近更新 更多