Iterate through a specific controls property c# - c#

i need to iterate through a specific controls property and save the that control's property name & value in xml file. i wrote few line but getting error.
private void SaveStyle()
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(Application.ExecutablePath+ #"\Products.xml", settings);
PropertyInfo[] properties = metroStyleManager1.GetType().GetProperties();
writer.WriteStartDocument();
foreach (PropertyInfo pi in properties)
{
writer.WriteElementString(pi.Name,pi.GetValue(((object)metroStyleManager1),null));
}
}
this line is giving error writer.WriteElementString(pi.Name,pi.GetValue(((object)metroStyleManager1),null));
next issue which i need to do that i have to read back the controls property data from xml file and set the value as per the controls name. which is not clear to me that how to do it. so please help. thanks
UPDATE
my full code to save controls property & read back too.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;
using MetroFramework;
namespace CSRAssistant
{
class Utils
{
public static void SaveProperty(System.ComponentModel.Component _Control)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml", settings);
PropertyInfo[] properties = _Control.GetType().GetProperties();
writer.WriteStartElement("metroStyleManager");
foreach (PropertyInfo pi in properties)
{
writer.WriteElementString(pi.Name, Convert.ToString(pi.GetValue(_Control, null)));
}
writer.WriteEndDocument();
writer.Flush();
writer.Close();
}
public static void ReadProperty(System.ComponentModel.Component _Control)
{
string _property = "", _value = "";
if (System.IO.File.Exists(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml"))
{
XmlReader rdr = XmlReader.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml");
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
if (rdr.LocalName.ToUpper() != "METROSTYLEMANAGER")
{
_property = rdr.LocalName;
_value = rdr.ReadInnerXml();
if (_property.ToUpper() == "STYLE")
((MetroFramework.Components.MetroStyleManager)_Control).Style = (MetroColorStyle)Enum.Parse(typeof(MetroColorStyle), _value);
if (_property.ToUpper() == "THEME")
((MetroFramework.Components.MetroStyleManager)_Control).Theme = (MetroThemeStyle)Enum.Parse(typeof(MetroThemeStyle), _value);
//else
// _Control.GetType().GetProperty(_property).SetValue(_Control, _value, null);
}
}
}
rdr.Close();
}
}
}
}

writer.WriteElementString(string localName, string value)
expects two string arguments. But pi.GetValue() returns value of type object. You need to convert second parameter to string:
Convert.ToString(pi.GetValue(metroStyleManager1))
That will check if value of object is not null, and return empty string if value is null. It also will check if object implements IConvertible or IFormattable interfaces and call appropriate ToString() method.

The exact error would surely help, but I guess your problem is the WriteElementString method which takes two string parameters.
PropertyInfo.GetValue on the other hand returns an object. You have to convert that object to a string. A possible way would be to call .ToString() on it if it's not null and use an empty string if it is.
foreach (PropertyInfo pi in properties)
{
object obj = pi.GetValue(metroStyleManager1, null);
writer.WriteElementString(pi.Name, obj != null ? obj.ToString() : String.Empty);
}

Related

XML Outputter adds extra non ascii character

I am using following XML outputter for writing xml files on basis of CSV data.
public override void Output(IRow input, IUnstructuredWriter output)
{
IColumn badColumn = input.Schema.FirstOrDefault(col => col.Type != typeof(string));
if (badColumn != null)
{
throw new ArgumentException(string.Format("Column '{0}' must be of type 'string', not '{1}'", badColumn.Name, badColumn.Type.Name));
}
using (var writer = XmlWriter.Create(output.BaseStream, this.fragmentSettings))
{
writer.WriteStartElement(this.rowPath);
foreach (IColumn col in input.Schema)
{
var value = input.Get<string>(col.Name);
if (value != null)
{
// Skip null values in order to distinguish them from empty strings
writer.WriteElementString(this.columnPaths[col.Name] ?? col.Name, value);
}
}
}
}
It works really fine and jobs finishes completely without any errors however, on preview and downloading the file there is another extra character which causes in failure of that xml file being read. I have tried with fragment level and Auto as conformance levels.
My sample output obtained is
and the extra character between the 2 tags is causing problem while reading the file.
I have solved the problem by explicitly providing the encoding settings as well as closing tags with the code below
private XmlWriterSettings fragmentSettings = new XmlWriterSettings
{
ConformanceLevel = ConformanceLevel.Auto,
Encoding = Encoding.UTF8
};
public override void Output(IRow input, IUnstructuredWriter output)
{
IColumn badColumn = input.Schema.FirstOrDefault(col => col.Type != typeof(string));
if (badColumn != null)
{
throw new ArgumentException(string.Format("Column '{0}' must be of type 'string', not '{1}'", badColumn.Name, badColumn.Type.Name));
}
using (var writer = XmlWriter.Create(output.BaseStream, this.fragmentSettings))
{
writer.WriteStartElement(this.rowPath);
foreach (IColumn col in input.Schema)
{
var value = input.Get<string>(col.Name);
if (value != null)
{
// Skip null values in order to distinguish them from empty strings
writer.WriteElementString(this.columnPaths[col.Name] ?? col.Name, value);
}
}
writer.WriteEndElement(); //explicit closing tag for stream
}
}
This outputs a well formed XML which can be easily read with any xml reader.

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

XmlAttributeOverrides attaching an attribute to an element

I have the following code which is throwing an error...
The error...
For non-array types, you may use the following attributes: XmlAttribute, XmlText, XmlElement, or XmlAnyElement.
The code (last line in Go method is throwing the exception)...
public void Go(Type typeToSerialize, object itemToSerialize)
{
Dictionary<string, bool> processedList = new Dictionary<string, bool>();
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
AttachXmlTransforms(overrides, itemToSerialize.GetType(), processedList);
s = new XmlSerializer(typeToSerialize, overrides);
}
private static void AttachXmlTransforms(XmlAttributeOverrides overrides, Type root,
Dictionary<string, bool> processedList)
{
foreach (PropertyInfo pi in root.GetProperties())
{
string keyName = pi.DeclaringType + "-" + pi.Name;
if ((pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?))
&& !processedList.ContainsKey(keyName))
{
XmlAttributes attributes = new XmlAttributes();
attributes.XmlElements.Add(new XmlElementAttribute(pi.Name));
//attributes.XmlAnyAttribute = new XmlAnyAttributeAttribute();
attributes.XmlAttribute = new XmlAttributeAttribute("dval");
//attributes.XmlIgnore = true;
processedList.Add(keyName, true);
overrides.Add(pi.DeclaringType, pi.Name, attributes);
}
if (pi.MemberType == MemberTypes.Property && !pi.PropertyType.IsPrimitive
&& pi.PropertyType.IsPublic && pi.PropertyType.IsClass
&& pi.PropertyType != typeof(DateTime))
{
AttachXmlTransforms(overrides, pi.PropertyType, processedList);
}
}
}
I'm attempting to add an attribute (dval) to only DateTime elements (this is an external requirement)...
From this...
<CreatedDate>01/01/2012</CreatedDate>
To this...
<CreatedDate dval="01/01/2012">01/01/2012</CreatedDate>
Is there a way to add an attribute to a normal non-array type element?
I presume that you're having trouble with the line
attributes.XmlElements.Add(new XmlElementAttribute(pi.Name))
First off, judging by the naming convention, I'm guessing that XmlElements is not an Xml Element, but it's a collection of elements.
Also, judging by your error message, the XmlElements Add method doesn't take your XmlElementAttribute as a parameter, instead it takes a XmlAttribute, XmlText, XmlElement, or XmlAnyElement.
I ended up approching this a different way...
1) Convert the object to XML
2) Run the following...
using (MemoryStream memStm = new MemoryStream())
{
// Serialize the object using the standard DC serializer.
s.WriteObject(memStm, graph);
// Fix the memstream location.
memStm.Seek(0, SeekOrigin.Begin);
// Load the serialized document.
XDocument document = XDocument.Load(memStm);
foreach (KeyValuePair<string, ItemToAmend> kvp in _processedDateTimes)
{
// Locate the datetime objects.
IEnumerable<XElement> t = from el in document.Descendants(XName.Get(kvp.Value.ProperyName, kvp.Value.PropertyNamespace))
select el;
// Add the attribute to each element.
foreach (XElement e in t)
{
string convertedDate = string.Empty;
if (!string.IsNullOrEmpty(e.Value))
{
DateTime converted = DateTime.Parse(e.Value);
convertedDate = string.Format(new MyBtecDateTimeFormatter(), "{0}", converted);
}
e.Add(new XAttribute(XName.Get("dval"), convertedDate));
}
}
// Write the document to the steam.
document.Save(writer);
}

Xml deserialization appends to list

I'm trying to deserialize some settings from an xml file. The problematic property/underlying field is one called AlertColors. I initialize the underlying field to white, yellow, and red to make sure that a new instance of this class has a valid color setting. But when I deserialize, _colorArgb ends up with six values, first three are the initialization values and the last three are the ones that are read from the xml file. But the property AlertColors do not append to the field, but changes its elements. Why do I end up with a field with six colors?
Here's the code:
private List<int> _colorArgb = new List<int>(new int[] { Color.White.ToArgb(), Color.Yellow.ToArgb(), Color.Red.ToArgb() });
public List<int> AlertColors
{
get
{
return _colorArgb;
}
set
{
for (int i = 0; i < Math.Min(_colorArgb.Count, value.Count); i++)
{
if (_colorArgb[i] != value[i])
{
HasChanged = true;
}
}
_colorArgb = value;
}
}
public bool Deserialize(string filePath)
{
if (!File.Exists(filePath))
{
Logger.Log("Error while loading the settings. File does not exist.");
return false;
}
FileStream fileStream = null;
try
{
fileStream = new FileStream(filePath, FileMode.Open);
System.Xml.Serialization.XmlSerializerFactory xmlSerializerFactory =
new XmlSerializerFactory();
System.Xml.Serialization.XmlSerializer xmlSerializer =
xmlSerializerFactory.CreateSerializer(typeof(Settings));
Settings deserializedSettings = (Settings)xmlSerializer.Deserialize(fileStream);
GetSettings(deserializedSettings);
Logger.Log("Settings have been loaded successfully from the file " + filePath);
}
catch (IOException iOException)
{
Logger.Log("Error while loading the settings. " + iOException.Message);
return false;
}
catch (ArgumentException argumentException)
{
Logger.Log("Error while loading the settings. " + argumentException.Message);
return false;
}
catch (InvalidOperationException invalidOperationException)
{
Logger.Log("Error while loading the settings. Settings file is not supported." +
invalidOperationException.Message);
return false;
}
finally
{
if (fileStream != null)
fileStream.Close();
FilePath = filePath;
}
return true;
}
protected void GetSettings(Settings settings)
{
AlertColors = settings.AlertColors;
}
And the relevant part of the xml file that I'm deserializing:
<AlertColors>
<int>-1</int>
<int>-15</int>
<int>-65536</int>
</AlertColors>
Basically, that's just how XmlSerializer works. Unless the list is null, it never expects to try and set a value. In particular, most of the time, sub-item lists don't have a setter - they are things like:
private readonly List<Child> children = new List<Child>();
public List<Child> Children { get { return children; } }
(because most people don't want external callers to reassign the list; they just want them to change the contents).
Because of this, XmlSerializer operates basically like (over-simplifying):
var list = yourObj.SomeList;
foreach({suitable child found in the data})
list.Add({new item});
One fix is to use an array rather than a list; it always expects to assign an array back to the object, so for an array it is implemented more like (over-simplifying):
var list = new List<SomeType>();
foreach({suitable child found in the data})
list.Add({new item});
yourObj.SomeList = list.ToArray();
However, for a fixed number of values, a simpler implementation might be just:
public Foo Value1 {get;set;}
public Foo Value2 {get;set;}
public Foo Value3 {get;set;}
(if you see what I mean)
To get your desired result without changing your data types, you could use a DataContractSerializer (using System.Runtime.Serialization;) instead of the normal XmlSerializer. It doesn't call default constructors therefore you will end up with 3 colours instead of 6.
var ser = new DataContractSerializer(typeof(Settings));
var reader = new FileStream(#"c:\SettingsFile.xml", FileMode.Open);
var deserializedSettings = (Settings)ser.ReadObject(reader);
Coming a bit late to the party, but I just ran into this problem as well.
The accepted answer mentions that Arrays are assigned to every time a deserialization occurs. This was very helpful. But I needed a solution that didn't require me to change the type of the properties and rewrite a million lines of code. So I came up with this:
Using XML Serializer attributes you can 'redirect' the serializer to an Array that wraps around the original property.
[XmlIgnore]
public List<int> AlertColors { get; set; } = new List<int>() { Color.White.ToArgb(), Color.Yellow.ToArgb(), Color.Red.ToArgb() });
[XmlArray(ElementName = "AlertColors")]
public long[] Dummy
{
get
{
return AlertColors.ToArray();
}
set
{
if(value != null && value.Length > 0) AlertColors = new List<int>(value);
}
}
The Dummy property has to be public in order for the serializer to access it. For me however this was a small price to pay, leaving the original property unchanged so I didn't have to modify any additional code.

Categories