复制xaml子元素

时间:2015-10-01 18:06:43

标签: c# wpf xaml canvas

我正在开发一款Windows手机应用。我想将一个画布的孩子复制到其他画布上。我可以使用以下代码完成它,但问题是我必须先从一个画布中删除它。代码是:

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

我想在画布上保留这些元素。还有另一种方式吗?

3 个答案:

答案 0 :(得分:3)

不要尝试将相同的Template_canvas1.Children添加到root画布,而是首先复制那些Children,然后将副本添加到root画布。< / p>

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需要作为依赖。如果你想看一下学习代码,他在库中有示例解决方案。

答案 1 :(得分:1)

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

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

即。它们不会被真正克隆。相反,WPF将自动完全按照您的预期填充视觉元素的全新子树。由于模板允许您完全定义所有方面,因此没有与例如试图将事件订阅连接起来,正确设置绑定等等。

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

答案 2 :(得分:0)

用户反馈后无法在Windows Phone上运行的新答案

可以下载完整的最终Windows Phone应用程序here

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

其次,我在不知情的情况下没有检查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手机,但在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);
}