How to Clone a Wpf Object

How can you clone a WPF object?

The simplest way that I've done it is to use a XamlWriter to save the WPF object as a string. The Save method will serialize the object and all of its children in the logical tree. Now you can create a new object and load it with a XamlReader.

ex:
Write the object to xaml (let's say the object was a Grid control):

string gridXaml = XamlWriter.Save(myGrid);

Load it into a new object:

StringReader stringReader = new StringReader(gridXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Grid newGrid = (Grid)XamlReader.Load(xmlReader);

How to clone an object?

First of all, the error is very easily explained: ItemCollection doesn't have a public constructor with 0 parameters. AnyClone obviously has to instanciate the item it wants to clone, which ItemCollection does not support, at least not without supplying parameters.

This shouldn't matter though, you're just using the wrong approach to copying your data.

ItemCollection is a View, that - from the supplied data binding - is used as a layer between your data and your TreeView, so you can apply sorting/filtering/grouping without changing the data. If you want to copy data from one TreeView to another, don't copy the data in the ItemCollection, copy the data in the ItemsSource.

Also, you're trying to clone all of the items, instead of just the one you want to copy. Even if you get around the error above: you try to copy one TreeView's complete view and then add it as an item to the other TreeView, which isn't what you're trying to achieve.

A much cleaner approach would be the following:

TreeView should have an ItemsSource: Bind this ItemsSource to the data you want your TreeView to display. Make sure CategoryList_Copy has a separate collection bound to its ItemsSource. When copying data, just copy the value from one CategoryList's ItemsSource over to the other. There you should be able to easily use AnyClone's cloning method.

Cloning WPF controls and object hierarchies

If you are attempting to clone UIElements, use the XamlWriter to save to a string, though no method of cloning is foolproof. You then use XamlReader to load a copy. You could still run into issues. Things like handlers not being copied, x:Name being duplicated, etc. But for simple elements like Grid or Brush, it works great.

Edit 1: There is no generic way to clone anything, however:

1) If a clone function is written correctly, it will call the base Clone for you, and copy the base class stuff, too. If it isn't written correctly calling the base Clone won't help much, though you can call a private method this way.

2) If you have to, you can use Reflection to copy pretty much anything, even click handlers, from an old object to a new object.

3) XamlWriter and XamlReader will copy and instantiate heirarchies of objects, just minus certain properties.

Edit 2: Here's a common cloning design pattern I use in my class hierarchies. Assume base class shape, derived class circle:

protected Circle(Circle t)
{
CopyFrom(t);
}

public override object Clone()
{
return new Circle(this);
}

protected void CopyFrom(Circle t)
{
// Ensure we have something to copy which is also not a self-reference
if (t == null || object.ReferenceEquals(t, this))
return;

// Base
base.CopyFrom((Shape)t);

// Derived
Diameter = t.Diameter;
}

Cloning an object does not update bound values in UI - WPF

How do you expect the UI to know that you changed the value of the property? You never told it.

You should move BillAddress and DelAddress to a viewmodel class that implements INotifyPropertyChanged and make that your DataContext.

This code is C#7. If you're using an earlier version, let me know and I'll fix it to be compatible with your compiler.

MainWindow.xaml.cs

public MainWindow() {
InitializeComponent();

DataContext = new MainViewModel();
}

public MainViewModel ViewModel => (MainViewModel)DataContext;

private void CopyDelAddress(object sender, RoutedEventArgs e) {
ViewModel.BillAddress = ViewModel.DelAddress.Clone();

// Values ARE NOT copied to BillAddress. A clone of DelAddress
// is assigned to BillAddress.
}

ViewModels.cs

public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
BillAddress = new Address();
DelAddress = new Address();
}

private Address _billAddress = null;
public Address BillAddress
{
get { return _billAddress; }
set
{
if (value != _billAddress)
{
_billAddress = value;
OnPropertyChanged();
}
}
}

private Address _delAddress = null;
public Address DelAddress
{
get { return _delAddress; }
set
{
if (value != _delAddress)
{
_delAddress = value;
OnPropertyChanged();
}
}
}
}

public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}

Copy Clone with mvvm

OK, I was interested so I thought I give it a try and make a sample program myself.
I can't give you my code to just copy, but here is some basic structure that worked for me:

View:

  • A ListBox with Textbox as Itemtemplate. ItemSource is bound to a ObservableCollection<StringModel> StringList in the ViewModel, which I will come to later. SelectedItem is Bound to a single StringModel SelectedString instance in the ViewModel
  • Clone Button
  • Copy Button
  • In the code behind (or wherever you want) simply set up your ViewModel as datacontext and the firing of the corresponding functions in the ViewModel, in case the buttons are pressed (next point)

StringModel
This is as simple as it gets, a wrapper class around a string, in order to A) pass as reference and B) implement INotifyPropertyChanged. Notice the second constructer taking in a string as argument.

public class StringModel : ViewModelBase
{
private string stringValue;

public string StringValue
{
get { return stringValue; }
set { SetProperty<string>(ref stringValue, value); }
}

public StringModel(){ }

public StringModel(string value)
{
StringValue = value;
}
}

ViewModel
This class simply holds the Collection mentioned above as well as the property of the currently selected item. Additionally we have two functions corresponding to the buttons:

public void AddCopy()
{
if (SelectedString == null)
return;

StringList.Add(new StringModel(SelectedString.StringValue));
}

public void AddClone()
{
if (SelectedString == null)
return;
StringList.Add(SelectedString);
}

As you can see, since your StringModel is now a separate class, you can pass it by reference, or in case of copying, simply create a new one with the string content.

Obviously there are some additional checks to be make (hint: null pointer exception with the ObservableCollection, INotifyPropertyChanged implementation of ViewModel Properties), but this should get you started. Ask away if you have any questions.

Deep cloning objects

Whereas one approach is to implement the ICloneable interface (described here, so I won't regurgitate), here's a nice deep clone object copier I found on The Code Project a while ago and incorporated it into our code.
As mentioned elsewhere, it requires your objects to be serializable.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep copy of the object via serialization.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>A deep copy of the object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(source));
}

// Don't serialize a null object, simply return the default for that object
if (ReferenceEquals(source, null)) return default;

using var Stream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}

The idea is that it serializes your object and then deserializes it into a fresh object. The benefit is that you don't have to concern yourself about cloning everything when an object gets too complex.

In case of you prefer to use the new extension methods of C# 3.0, change the method to have the following signature:

public static T Clone<T>(this T source)
{
// ...
}

Now the method call simply becomes objectBeingCloned.Clone();.

EDIT (January 10 2015) Thought I'd revisit this, to mention I recently started using (Newtonsoft) Json to do this, it should be lighter, and avoids the overhead of [Serializable] tags. (NB @atconway has pointed out in the comments that private members are not cloned using the JSON method)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (ReferenceEquals(source, null)) return default;

// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}


Related Topics



Leave a reply



Submit