How can you clone a WPF object? - c#

Anybody have a good example how to deep clone a WPF object, preserving databindings?
The marked answer is the first part.
The second part is that you have to create an ExpressionConverter and inject it into the serialization process. Details for this are here:
http://www.codeproject.com/KB/WPF/xamlwriterandbinding.aspx?fid=1428301&df=90&mpp=25&noise=3&sort=Position&view=Quick&select=2801571

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

In .NET 4.0, the new xaml serialization stack makes this MUCH easier.
var sb = new StringBuilder();
var writer = XmlWriter.Create(sb, new XmlWriterSettings
{
Indent = true,
ConformanceLevel = ConformanceLevel.Fragment,
OmitXmlDeclaration = true,
NamespaceHandling = NamespaceHandling.OmitDuplicates,
});
var mgr = new XamlDesignerSerializationManager(writer);
// HERE BE MAGIC!!!
mgr.XamlWriterMode = XamlWriterMode.Expression;
// THERE WERE MAGIC!!!
System.Windows.Markup.XamlWriter.Save(this, mgr);
return sb.ToString();

There are some great answers here. Very helpful. I had tried various approaches for copying Binding information, including the approach outlined in http://pjlcon.wordpress.com/2011/01/14/change-a-wpf-binding-from-sync-to-async-programatically/ but the information here is the best on the Internet!
I created a re-usable extension method for dealing with InvalidOperationException “Binding cannot be changed after it has been used.” In my scenario, I was maintaining some code somebody wrote, and after a major DevExpress DXGrid framework upgrade, it no longer worked. The following solved my problem perfectly. The part of the code where I return the object could be nicer, and I will re-factor that later.
/// <summary>
/// Extension methods for the WPF Binding class.
/// </summary>
public static class BindingExtensions
{
public static BindingBase CloneViaXamlSerialization(this BindingBase binding)
{
var sb = new StringBuilder();
var writer = XmlWriter.Create(sb, new XmlWriterSettings
{
Indent = true,
ConformanceLevel = ConformanceLevel.Fragment,
OmitXmlDeclaration = true,
NamespaceHandling = NamespaceHandling.OmitDuplicates,
});
var mgr = new XamlDesignerSerializationManager(writer);
// HERE BE MAGIC!!!
mgr.XamlWriterMode = XamlWriterMode.Expression;
// THERE WERE MAGIC!!!
System.Windows.Markup.XamlWriter.Save(binding, mgr);
StringReader stringReader = new StringReader(sb.ToString());
XmlReader xmlReader = XmlReader.Create(stringReader);
object newBinding = (object)XamlReader.Load(xmlReader);
if (newBinding == null)
{
throw new ArgumentNullException("Binding could not be cloned via Xaml Serialization Stack.");
}
if (newBinding is Binding)
{
return (Binding)newBinding;
}
else if (newBinding is MultiBinding)
{
return (MultiBinding)newBinding;
}
else if (newBinding is PriorityBinding)
{
return (PriorityBinding)newBinding;
}
else
{
throw new InvalidOperationException("Binding could not be cast.");
}
}
}

How about:
public static T DeepClone<T>(T from)
{
using (MemoryStream s = new MemoryStream())
{
BinaryFormatter f = new BinaryFormatter();
f.Serialize(s, from);
s.Position = 0;
object clone = f.Deserialize(s);
return (T)clone;
}
}
Of course this deep clones any object, and it might not be the fastest solution in town, but it has the least maintenance... :)

Related

How to compare two FlowDocuments?

I want to compare a FlowDocument to a document of Rich Text Box. Here is the code
if (rtbEditor.Document != (XamlReader.Parse(currentNote.content) as FlowDocument))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
At the beginning I set rtbEditor's document as
rtbEditor.Document = XamlReader.Parse(currentNote.content) as FlowDocument;
Thus, unless the content of rtbEditor is changed, I thought that the if statement should not execute,but it does. Probably this is not the way to compare FlowDocuments. If this is not the correct way then how can we compare two documents?
If it is necessary, the currentNote.content is a string containing xml content of FlowDocument.
Assuming you have no images in your FlowDocument instances, you can just serialize to XAML and compare the XAML. First, create extension methods to generate the XAML strings:
public static class FrameworkContentElementExtensions
{
public static string ToXaml(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
public static string ToFormattedXamlString(this FrameworkContentElement element)
{
if (element == null)
return null;
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb, settings))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
}
Then you can do
if (rtbEditor.Document.ToXaml() != currentNote.content)
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
Note that if the XAML differs only because of cosmetic formatting (XML indentation), since XAML documents are valid XML, you can parse your XAML to an XElement and use XNode.DeepEquals(). You can also serialize a FrameworkContentElement directly to an XElement without the intervening string representation for improved performance:
public static class FrameworkContentElementExtensions
{
public static XElement ToXamlXElement(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var doc = new XDocument();
using (var xmlWriter = doc.CreateWriter())
{
XamlWriter.Save(element, xmlWriter);
}
var xElement = doc.Root;
if (xElement != null)
xElement.Remove();
return xElement;
}
}
And then
var docXaml = rtbEditor.Document.ToXamlXElement();
var currentNoteXaml = XElement.Parse(currentNote.content);
if (!XNode.DeepEquals(docXaml, currentNoteXaml))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
If you are concerned there might be embedded messages and want to generate a warning message in this case, see Finding all Images in a FlowDocument.

remove type declaration from XML while serialize

I need to get clear xml without any namespace and type declarations. Here is the serialized xml:
<placeBetRequest p1:type="PlaceBetRequestTeamed" xmlns:p1="http://www.w3.org/2001/XMLSchema-instance">
...
</placeBetRequest>
Need to someway configure XmlSerializer to set it don't add any attributes but only mine (if I set them with [XmlAttribte]
the clean xml need to looks like this:
<placeBetRequest>
...
</placeBetRequest>
Here is my serialization method:
public static string XmlConvert<T>(T obj, params Type[] wellKnownTypes) where T : class
{
var emptyNamepsaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var settings = new XmlWriterSettings {Indent = true, OmitXmlDeclaration = true};
XmlSerializer serializer = wellKnownTypes == null
? new XmlSerializer(typeof(T))
: new XmlSerializer(typeof(T), wellKnownTypes);
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, obj, emptyNamepsaces);
return stream.ToString();
}
}
Please help. Thanks.
I've been using regex
string input =
"<p1:placeBetRequest p1:type=\"PlaceBetRequestTeamed\" xmlns:p1=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"</p1:placeBetRequest>";
string pattern = #"(?'prefix'\</?)(?'ns'[^:]*:)";
string output = Regex.Replace(input, pattern, "${prefix}");

XDocument is removing document comment

When I run <!-- This list contains names/words with specific casing - and specific to english only --> will be removed in new destination.
Is there any way to prevent it from happening. I event tried with XDocument.Root same thing happened.
Ps: I know method are not inside class :).
<!-- This list contains names/words with specific casing - and specific to english only -->
<ignore_list>
<name>A.M.</name>
<name>Aaron</name>
<name>Abbie</name>
<name>Abi</name>
<name>Abigail</name>
<name>Abolfazl</name>
<name>Adam</name>
</ignore_list>
private static void EnGBNamesEtc()
{
var listNames = new List<string>(){"some", "names");
var xele = XElement.Load(#"C:\Users\ivandro\Source\subtitleedit\Dictionaries\en_GB_names_etc.xml");
var names = listNames.Except(xele.Elements("name").Select(n => n.Value)).ToList();
foreach (var newName in names)
{
xele.Add(new XElement("name", newName));
}
SaveToNewXml(xele, #"D:\Backup\en_GB_names_etc1.xml");
}
private static void SaveToNewXml(XElement xelem, string dest)
{
XmlWriterSettings xws = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true };
var ordered = xelem.Elements("name").OrderBy(element => element.Value).ToList();
using (XmlWriter xw = XmlWriter.Create(dest, xws))
{
xelem.ReplaceAll(ordered);
xelem.Save(xw);
}
}
That's possibly because you're using XElement which only capable of representing single node (when you have two "root" nodes, <ignore_list> and the comment node). Use XDocument instead of XElement to keep the comment node, for example (I tried to keep minimal changes to your original code) :
private static async void EnGBNamesEtc()
{
var listNames = new List<string>(){"some", "names");
var xdoc = XDocument.Load(#"C:\Users\ivandro\Source\subtitleedit\Dictionaries\en_GB_names_etc.xml");
var names = listNames.Except(xdoc.Root.Elements("name").Select(n => n.Value)).ToList();
foreach (var newName in names)
{
xdoc.Root.Add(new XElement("name", newName));
}
SaveToNewXml(xdoc, #"D:\Backup\en_GB_names_etc1.xml");
}
private static void SaveToNewXml(XDocument xdoc, string dest)
{
XmlWriterSettings xws = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true };
var ordered = xdoc.Root.Elements("name").OrderBy(element => element.Value).ToList();
using (XmlWriter xw = XmlWriter.Create(dest, xws))
{
xdoc.Root.ReplaceAll(ordered);
xdoc.Save(xw);
}
}

Deserialized object not same as source

I'm having problem deserializing previously serialized XML.
My class is generated from .xsd by xsd.exe utility. I have no influence on the structure of .xsd as it was issued by government, and used to standardize communication...
The problem is, when I create an object, set some properties on it, then serialize it using XmlSerializer, and then deserialize it back, I do not get the same "contents" as I started with. Some of the elements from XML deserialize in "Any" property instead of properties from which these elements were serialized in a first place.
Perhaps a clumsy explanation, but I've created a sample project that reproduces my issue.
Sample project can be found here.
Edit:
Ok, here is some sample code. Unfortunately I can't paste everything here, because file generated by xsd.exe is over 4000 lines long. But everything required is in linked file.
My test console app:
static void Main(string[] args)
{
Pismeno pismeno = new Pismeno();
#region Build sample content
pismeno.Sadrzaj = new SadrzajTip();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml("<SomeXml xmlns=\"http://some.namespace.com/\">Some content goes here</SomeXml>");
pismeno.Sadrzaj.Item = xmlDoc.DocumentElement;
pismeno.Prilog = new PismenoPrilog[1];
pismeno.Prilog[0] = new PismenoPrilog();
pismeno.Prilog[0].VrijemeNastanka = DateTime.Now.ToString("s");
XmlDocument xmlTitle = new XmlDocument();
xmlTitle.LoadXml("<Title xmlns=\"http://some.namespace.com/\">Test title 1</Title>");
pismeno.Prilog[0].Any = new XmlElement[1];
pismeno.Prilog[0].Any[0] = xmlTitle.DocumentElement;
pismeno.Prilog[0].Sadrzaj = new SadrzajTip();
EAdresaTip eat = new EAdresaTip();
eat.URL = "http://www.example.com/testfile.doc";
pismeno.Prilog[0].Sadrzaj.Item = eat;
#endregion
// Serialize object, and then deserialize it again
string pismenoSer = Serialize(pismeno);
Pismeno pismeno2 = Deserialize<Pismeno>(pismenoSer);
// Objects to compare. "source" has source.Sadrzaj and source.Prilog properties set
// "shouldBeTheSameAsSource" has shouldBeTheSameAsSource.Any property set
Pismeno source = pismeno;
Pismeno shouldBeTheSameAsSource = pismeno2;
}
public static string Serialize(object o)
{
string ret = null;
using (var stream = new MemoryStream())
{
XmlWriter xw = new XmlTextWriter(stream, Encoding.UTF8) { Formatting = Formatting.Indented };
new XmlSerializer(o.GetType()).Serialize(xw, o);
stream.Flush();
stream.Seek(0, SeekOrigin.Begin);
ret = (new StreamReader(stream, Encoding.UTF8)).ReadToEnd();
}
return ret;
}
public static T Deserialize<T>(string xml)
{
return (T)new XmlSerializer(typeof(T)).Deserialize(XmlReader.Create(new StringReader(xml)));
}

Object change in C#

When I write this code I see an unexpected situation how can I solve this?
KurumReferans tempReferans = new KurumReferans();
tempReferans = kRef;
if (kurumDetaylari.IsTakipMekanizmasiKullaniyor == true)
{
KurumReferans kRefIstakip = new KurumReferans();
kRefIstakip = kRef;
kRefIstakip.Referans = "SORUMLU";
kRefIstakip.Yontem = "SORUMLU:";
kRefIstakip.Tipi = Tipi.Zorunlu;
kRefIstakip.Parent = kurum;
PostAddEdit(db.KurumReferans, kRefIstakip, cmd, "", "", "", "");
}
Firstly I assign,
tempReferans = kRef;
After when I assign kref to other object,
KurumReferans kRefIstakip = new KurumReferans();
kRefIstakip = kRef;
kRefIstakip.Referans = "SORUMLU";
tempReferans object's values change but I want to old values.
Your object is getting changed, because when you assign an object, it just assigns the address to it and both variable uses same memory space or object. To overcome this, you have to make a deep copy of the object and assign.
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
EDIT: You have mark that class with attribute [Serializable]
In the line:
kRefIstakip = kRef;
the object kRef is also referenced by kRefIstakip. Because you are assigning kRef instance to kRefIstakip, not copying kRef to kRefIstakip.
In Reference Type objects, the code:
obj1 = obj2;
doesn't copy the values of obj2 to obj1, but it copies obj2 reference to obj1. And then both can access same memory location.

Categories