4

I am working on a windows phone app. I want to copy children of one canvas to other canvas. I can do it with the following code but the problem is I have to remove it from one canvas first. Code is:

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

I want to keep these elements on both the canvas. Is there another way?

Lynn Crumbling
  • 12,985
  • 8
  • 57
  • 95
NotABot
  • 516
  • 1
  • 8
  • 27

3 Answers3

4

Instead of trying to add the same Template_canvas1.Children to the root canvas, first make a copy of those Children and then add the copy to the root canvas.

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

Then change your loop to:

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

I haven't tested this code, so you may have to modify it a bit, but it should put you in the right direction.

Alternatively, you can probably use the code below which is copied from 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);
              }
            }
          }
        }
      }
    }
  }
} 

If none of these worked for you, I'm afraid you'd have to implement your own serializer. It looks like David Poll implemented a decent serlizer, so have a look. Using his serlizer is as simple as using the XamlWriter, then you can use the 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);
}

To get this functionality, download his Slab library, go to the "Binaries" folder and copy all the dlls that start with "SLaB.Utilities.Xaml.Serializer" to your project. There might be some other dlls required as dependency. He has example solution in the library if you like to look at the code an learn.

Community
  • 1
  • 1
Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • How do I make copy of those children? – NotABot Oct 01 '15 at 18:22
  • Check [this post](http://www.davidpoll.com/2010/07/25/to-xaml-with-love-an-experiment-with-xaml-serialization-in-silverlight/), it has an implementation of Xaml serlializer which may do the trick for you. – Racil Hilan Oct 01 '15 at 20:36
  • See my updated answer. I've added a class that does the cloning. – Racil Hilan Oct 01 '15 at 20:58
  • Windows.UI.Xaml not supported in windows phone sdk – NotABot Oct 03 '15 at 08:42
  • OK, but have you checked [this post](http://www.davidpoll.com/2010/07/25/to-xaml-with-love-an-experiment-with-xaml-serialization-in-silverlight/) which I mentioned in my earlier comment? It should work on Windows Phone. I can added it to the answer if you like. – Racil Hilan Oct 03 '15 at 19:47
  • yes I have checked that post but XamlWriter doesn't work with windows phone, I dont know how to implement that. Can you please add it to the answer? – NotABot Oct 08 '15 at 14:44
  • The post is exactly to solve that problem. He implemented a serializre that is probably even better than the `XamlWriter`. Just download his library and copy the dlls to your project. I updated my answer with the details as you requested. – Racil Hilan Oct 08 '15 at 20:28
  • It copies all attributes, but I'm not sure about the events. I think it does, but you will have to try it to confirm. – Racil Hilan Oct 09 '15 at 13:40
1

Without a good Minimal, Complete, and Verifiable example that shows clearly what you've tried, with a precise description of what exactly you're trying to achieve, it's impossible to know for sure what the best answer would be.

That said, given that WPF already knows how to "clone" elements in a sense, through the use of data templates, your question really sounds a lot like an XY Problem to me. That is, you only think you need to literally clone the elements already in your visual tree, when in fact what you should be doing is defining a view model that represents the data to be displayed for the element(s) to be "cloned", define a single data template that uses XAML to describe the visual elements that will display the data in the view model, and then simply apply the template as necessary wherever you want the visual elements to be "cloned".

I.e. they won't really be cloned. Instead, WPF will automatically populate a whole new sub-tree of visual elements exactly as you want them to be. Since the template allows you to completely define all aspects, there is no issue related to e.g. trying to get the event subscriptions hooked up, setting up bindings correctly, etc.

In your specific example (vague though it is), it sounds like you most likely want to use an ItemsControl element, in which the ItemsPanel is a Canvas object. You would then define a DataTemplate that represents a single item in the ItemsPanel; this template would be referenced either implicitly by setting its DataType property, or explicitly by setting the ItemsControl.ItemTemplate property. Then, instead of cloning anything, you just create an ItemsControl when you want a copy of your visual for the data.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
0

New answer after user's feedback that it is not working on Windows Phone

Complete final Windows Phone App can be downloaded here.

There are some API differences, for example instead of pinfo.SetMethod property we have to use pinfo.GetSetMethod() etc.

Secondly, I was unknowingly didn't check for Name property which must not be copied as otherwise we would be making another instance with same name.

Third, I posted for simple case of simple controls like Button, TextBox, Rectangle etc which do not contain children. If that is the case you have to go for recursive deep cloning to clone children too. As children could have more children and so on.

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

And if you are going for truly deep cloning then replace code in outer try block above with a simpler:

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

Old answer based on WPF only

Don't know about windows phone but in WPF this creates a fresh element and puts it in exactly same place in another canvas. Check if it fits your needs, else I will update it again.

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);
}
Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38