6

I have tried this approach first but getting error "Element is already the child of another element"

var objClone = new MyImageControl();
objClone = this;
((Canvas)this.Parent).Children.Add(objClone);

Then I checked this and this, but XamlWriter and XamlReader is not available in WinRT. I have tried to use MemberwiseClone() but it throws exception, "COM object that has been separated from its underlying RCW cannot be used. System.Runtime.InteropServices.InvalidComObjectException". So can anyone tell me how can I clone the existing UserControl in my canvas to itself ?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Farhan Ghumra
  • 15,180
  • 6
  • 50
  • 115

2 Answers2

3

I have written a UIElement extension that copies the properties and children of an element -- note that it does not set up an events for the clone.

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

Feel free to use this code if you find it useful.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Dr Herbie
  • 3,930
  • 1
  • 25
  • 28
1

You can try serializers other than XamlWriter and XamlReader to achieve the same effect described by your links. For example, use ServiceStack.Text to JSON serialize your object to a string, then get a new object from that string and add it to the parent.

Boluc Papuccuoglu
  • 2,318
  • 1
  • 14
  • 24
  • I tried Json.Net as ServiceStack.Text is not available in WinRT. Unfortunaly it is throwing exception while serialization. When I used `JsonConvert.SerializeObject(this)` it throws `StackOverflowException` and when I used `JsonConvert.SerializeObjectAsync(this)` it throws `JsonSerializationException` (Error getting value from 'Watermark' on 'Callisto.Controls.WatermarkTextBox') please note my user controls use other user controls from toolkit like [Callisto](https://github.com/timheuer/callisto) – Farhan Ghumra Mar 22 '13 at 09:42
  • 1
    Then I would suggest not cloning the MyImageControl at all. If the effect you want to achieve is, for example, putting two smiley faces on the canvas which are duplicates of each other, add a newly created MyImageControl, and set the imagesource (or whatever is appropriate) property of the new control identical to the first. If you say there are too many properties to copy by hand, there are methods that use reflection to loop through available properties and copy their values. For an example, see http://stackoverflow.com/questions/1198886/c-sharp-using-reflection-to-copy-base-class-properties – Boluc Papuccuoglu Mar 22 '13 at 11:15
  • The above solution is also not working in WinRT as there's limited method support in WinRT for [System.Reflection](http://msdn.microsoft.com/en-us/library/42892f65.aspx) like `Type.GetFields()` is not available in WinRT. – Farhan Ghumra Mar 22 '13 at 12:18
  • Finally I managed to code `CloneHelper` class but output is not upto the mark. I will update question with more details soon, thanks Boluc for your help. – Farhan Ghumra Mar 22 '13 at 13:02
  • `UIElement` is a reference type, therefore your code is equivalent to just `((Canvas)this.Parent).Children.Add (this)` — I hope you can see that this is not going to work. – Anton Tykhyy Sep 03 '13 at 13:20
  • @Anton, when saying "this is not going to work," are you referring to the code in the OP's question, or my suggestion of serializing it then deserializing it as a new object? – Boluc Papuccuoglu Sep 03 '13 at 14:50
  • I meant the original code snippet in OP's question. BTW serializing/deserializing won't work well either, WPF objects are not that simple. One could try serializing/deserializing as BAML, but even so I'd expect lots of nasty problems. I suspect OP is trying to solve a problem the wrong way; [he's solved the easy part, and now he wants help with the impossible part](http://blogs.msdn.com/b/oldnewthing/archive/2013/05/01/10415241.aspx). – Anton Tykhyy Sep 03 '13 at 16:33