Edit item in a dynamically created list of objects - c#

I have a C# winforms application. In this application I am creating a list of CanMessage objects dynamically from an xml file.
The xml file is constructed as below
<message id="0x641" ecu="BCM" name="BODY9" dlc="8" cyclicrate="500">
<bytes>
<byte0>0x0</byte0>
<byte1>0x0</byte1>
<byte2>0x0</byte2>
<byte3>0x0</byte3>
<byte4>0x0</byte4>
<byte5>0x0</byte5>
<byte6>0x0</byte6>
<byte7>0x0</byte7>
</bytes>
The Canmessage object is constructed like this:
CanMessage(String name,String _EcuName, ulong id, ushort dlc,int[] bytearray , int cyclic)
{
this.Name = name;
this.EcuName = _EcuName;
this.Id = id;
this.Dlc = dlc;
this.Bytes = new int[dlc];
this.CyclicRate = cyclic;
int i = 0;
for(i = 0; i < dlc; i++)
{
this.Bytes[i] = bytearray[i];
}
}
Below is how I am building my Canmessage list:
public void BuildCanList()
{
try
{
XmlDocument xd = new XmlDocument();
xd.Load(XmlFile);
XmlNodeList nodelist = xd.SelectNodes("/messages/message");
foreach (XmlNode n in nodelist)
{
String name, ecuname;
ulong id;
ushort dlc;
int[] bytes = new int[Convert.ToInt32(n.Attributes.GetNamedItem("dlc").Value)];
int cyclic;
name = n.Attributes.GetNamedItem("name").Value.ToString();
id = (ulong)Convert.ToInt32(n.Attributes.GetNamedItem("id").Value, 16);
ecuname = n.Attributes.GetNamedItem("ecu").Value.ToString();
dlc = (ushort)Convert.ToByte(n.Attributes.GetNamedItem("dlc").Value);
cyclic = Convert.ToInt32(n.Attributes.GetNamedItem("cyclicrate").Value);
XmlNode sn = n.SelectSingleNode("bytes");
for (ushort i = 0; i < dlc; i++)
{
try
{
bytes[i] = Convert.ToInt32(sn.ChildNodes.Item(i).InnerText, 16);
}
catch(Exception e)
{
bytes[i] = 0x0;
Console.WriteLine(String.Format("Error Building can Message: {0}", e.ToString()));
}
}
CanMessage cm = new CanMessage(name, ecuname, id, dlc, bytes, cyclic);
CanList.Add(cm);
}
My list is being created with no issues. My question is after my list has been created I will sometimes need to do some bit manipulation on certain bytes of a certain Canmessage. How can I select a message from the list based on its name property and then edit certain bytes from that message? I know how to select a message from the list using lambda expression and linq. But I don't know how to then combine that select method with an edit and save method or if this is even the best way to go about doing this.

If i understand your problem statement correctly, you need to find a specific CanMessage in the List<CanMessage> and edit its properties to your liking. Since your CanMessage is an object, it's being accessed by reference and therefore your edits will get reflected everywhere you reference it from.
Consider the following:
{
var CanList = new List<CanMessage>(); // I am assuming this is what it is
BuildCanList(CanList);
var messagetoEdit = CanList.First(m => m.Name == "BODY9");
messagetoEdit.Bytes[1]= 0xff;
messagetoEdit.Bytes[2]= 0xff;
messagetoEdit.Bytes[3]= 0xff;
messagetoEdit.Bytes[4]= 0xff;
var newMessagetoEdit = CanList.First(m => m.Name == "BODY9"); // you will see that values have changed here
//in case you wanted to serialise the list back, heres a snippet on how you could do it, for more details see https://stackoverflow.com/questions/6161159/converting-xml-to-string-using-c-sharp
//this is just to prove a point that changing bytes has effect,
StringWriter sw = new StringWriter();
var serialiser = new XmlSerializer(typeof(List<CanMessage>));
serialiser.Serialize(sw, CanList);
sw.ToString();
}
I hope this clarifies

Related

How to get and set value for /BSIColumnData of an annotation PDF itext c#

How to get and set value for /BSIColumnData of an annotation (markup) in PDF using itext c# as attached file?
I'm using Itext7 code below, but it is error at BSIColumnData:
public void BSIcontents ()
{
string pdfPath = #"C:\test PDF.pdf";
iText.Kernel.Pdf.PdfReader pdfReader = new iText.Kernel.Pdf.PdfReader(pdfPath);
iText.Kernel.Pdf.PdfDocument pdfDoc = new iText.Kernel.Pdf.PdfDocument(pdfReader);
int numberOfPages = pdfDoc.GetNumberOfPages();
int z = 0;
for (int i = 1; i <= numberOfPages; i++)
{
iText.Kernel.Pdf.PdfDictionary page = pdfDoc.GetPage(i).GetPdfObject();
iText.Kernel.Pdf.PdfArray annotArray = page.GetAsArray(iText.Kernel.Pdf.PdfName.Annots);
if (annotArray == null)
{
z++;
continue;
}
int size = annotArray.Size();
for (int x = 0; x < size; x++)
{
iText.Kernel.Pdf.PdfDictionary curAnnot = annotArray.GetAsDictionary(x);
if (curAnnot != null)
{
if (curAnnot.GetAsString(iText.Kernel.Pdf.PdfName.BSIColumnData) != null)
{
MessageBox.Show("BSIColumnData: " + curAnnot.GetAsString(iText.Kernel.Pdf.PdfName.BSIColumnData).ToString());
}
}
}
}
pdfReader.Close();
}
In Bluebeam Revu, you can see as below:
In Itext-rups 5.5.9, you can see as below:
I see two errors:
You try to use the BSIColumnData name like this:
iText.Kernel.Pdf.PdfName.BSIColumnData
This assumes that there is already a static PdfName member for your custom name. But of course there isn't, there only are predefined members for standard names used in itext itself. If you want to work with other names, you have to create a PdfName instance yourself and use that instance, e.g. like this
var BSIColumnData = new iText.Kernel.Pdf.PdfName("BSIColumnData");
You try to retrieve the value of that name as string
curAnnot.GetAsString(iText.Kernel.Pdf.PdfName.BSIColumnData)
but it is clear from your RUPS screenshot that the value of that name is an array of strings. Thus, even after correcting as described in the first item GetAsString(BSIColumnData) will return null. Instead do
var BSIColumnData = new iText.Kernel.Pdf.PdfName("BSIColumnData");
var array = curAnnot.GetAsArray(BSIColumnData);
After checking if (array != null) you now can access the strings at their respective indices using array.GetAsString(index).

How do i add info from 2 different arrays to a file?

i have two arrays of the same length that i would like to use the contents. index[0] of both array a and b should be used together when i am saving to a json file.i am guessing i need to lopp thru them in order to access each index and save the contents to a template json every time. i also would like that it returns a toString() format of the json, so each iteration for each index returns something.
public string Show(string[] id, string[] msg)
{
// opening code for json file with jobject and jsontexreader
for (int i = 0; i <= id.Length; i++ )
{
Newid = id[i];
Newmsg = msgs[i];
// setting the data to the json file
JObject temp = (JObject)o1.SelectToken(path1);
temp["data"] = msg;
JObject tem = (JObject)o1.SelectToken(path2);
tem["ksid"] = id;
}
return ??;
}
Here is a simple example of accomplishing what you wanted to do
string Show(string[] id, string[] msg)
{
if (id.Length != msg.Length)
throw new Exception(nameof(id) + " is not the same length as " + nameof(msg));
List<object> data = new List<object>();
for (int i = 0; i < id.Length; i++)
{
data.Add(new
{
Ksid = id[i],
Data = msg[i]
});
}
return Newtonsoft.Json.JsonConvert.SerializeObject(data);
}

How to Merge items within a List<> collection C#

I have a implememtation where i need to loop through a collection of documents and based on certain condition merge the documents .
The merge condition is very simple, if present document's doctype is same as later document's doctype, then copy all the pages from the later doctype and append it to the pages of present document's and remove the later document from the collection.
Note : Both response.documents and response.documents[].pages are List<> collections.
I was trying this but was getting following exception Once I remove the document.
collection was modified enumeration may not execute
Here is the code:
int docindex = 0;
foreach( var document in response.documents)
{
string presentDoctype = string.Empty;
string laterDoctype = string.Empty;
presentDoctype = response.documents[docindex].doctype;
laterDoctype = response.documents[docindex + 1].doctype;
if (laterDoctype == presentDoctype)
{
response.documents[docindex].pages.AddRange(response.documents[docindex + 1].pages);
response.documents.RemoveAt(docindex + 1);
}
docindex = docindex + 1;
}
Ex:
reponse.documents[0].doctype = "BankStatement" //page count = 1
reponse.documents[1].doctype = "BankStatement" //page count = 2
reponse.documents[2].doctype = "BankStatement" //page count = 2
reponse.documents[3].doctype = "BankStatement" //page count = 1
reponse.documents[4].doctype = "BankStatement" //page count = 4
Expected result:
response.documents[0].doctype = "BankStatement" //page count = 10
Please suggest.Appreciate your help.
I would recommend you to look at LINQ GroupBy and Distinct to process your response.documents
Example (as I cannot use your class, I give example using my own defined class):
Suppose you have DummyClass
public class DummyClass {
public int DummyInt;
public string DummyString;
public double DummyDouble;
public DummyClass() {
}
public DummyClass(int dummyInt, string dummyString, double dummyDouble) {
DummyInt = dummyInt;
DummyString = dummyString;
DummyDouble = dummyDouble;
}
}
Then doing GroupBy as shown,
DummyClass dc1 = new DummyClass(1, "This dummy", 2.0);
DummyClass dc2 = new DummyClass(2, "That dummy", 2.0);
DummyClass dc3 = new DummyClass(1, "These dummies", 2.0);
DummyClass dc4 = new DummyClass(2, "Those dummies", 2.0);
DummyClass dc5 = new DummyClass(3, "The dummies", 2.0);
List<DummyClass> dummyList = new List<DummyClass>() { dc1, dc2, dc3, dc4, dc5 };
var groupedDummy = dummyList.GroupBy(x => x.DummyInt).ToList();
Will create three groups, marked by DummyInt
Then to process the group you could do
for (int i = 0; i < groupedDummy.Count; ++i){
foreach (DummyClass dummy in groupedDummy[i]) { //this will process the (i-1)-th group
//do something on this group
//groupedDummy[0] will consists of "this" and "these", [1] "that" and "those", while [2] "the"
//Try it out!
}
}
In your case, you should create group based on doctype.
Once you create groups based on your doctype, everything else would be pretty "natural" for you to continue.
Another LINQ method which you might be interested in would be Distinct. But I think for this case, GroupBy would be the primary method you would like to use.
Use only "for loop" instead of "foreach".
foreach will hold the collection and cannot be modified while looping thru it.
Here is an example using groupBy, hope this help.
//mock a collection
ICollection<string> collection1 = new List<string>();
for (int i = 0; i < 10; i++)
{
collection1.Add("BankStatement");
}
for (int i = 0; i < 5; i++)
{
collection1.Add("BankStatement2");
}
for (int i = 0; i < 4; i++)
{
collection1.Add("BankStatement3");
}
//merge and get count
var result = collection1.GroupBy(c => c).Select(c => new { name = c.First(), count = c.Count().ToString() }).ToList();
foreach (var item in result)
{
Console.WriteLine(item.name + ": " + item.count);
}
Just use AddRange()
response.documents[0].pages.AddRange(response.documents[1].pages);
it will merge all pages of document[1] with the document[0] into document[0]

Displaying the name of each module stored in the array ina listBox

I'm trying to display the modules names from the array to the listBox but I'm getting a "NullReferenceException was unhandled" error.
modules.xml
<?xml version="1.0" encoding="utf-8" ?>
<Modules>
<Module>
<MCode>3SFE504</MCode>
<MName>Algorithms and Data Structures</MName>
<MCapacity>5</MCapacity>
<MSemester>1</MSemester>
<MPrerequisite>None</MPrerequisite>
<MLectureSlot>0</MLectureSlot>
<MTutorialSlot>1</MTutorialSlot>
</Module>
</Modules>
form1.cs
Modules[] modules = new Modules[16];
Modules[] pickedModules = new Modules[8];
int modulecounter = 0, moduleDetailCounter = 0;
while (textReader.Read())
{
XmlNodeType nType1 = textReader.NodeType;
if ((nType1 != XmlNodeType.EndElement) && (textReader.Name == "ModuleList"))
{
// ls_modules_list.Items.Add("MODULE");
Modules m = new Modules();
while (textReader2.Read()) //While reader 2 reads the next 7 TEXT items
{
XmlNodeType nType2 = textReader2.NodeType;
if (nType2 == XmlNodeType.Text)
{
if (moduleDetailCounter == 0)
m.MCode = textReader2.Value;
if (moduleDetailCounter == 1)
m.MName = textReader2.Value;
if (moduleDetailCounter == 2)
m.MCapacity = textReader2.Value;
if (moduleDetailCounter == 3)
m.MSemester = textReader2.Value;
if (moduleDetailCounter == 4)
m.MPrerequisite = textReader2.Value;
if (moduleDetailCounter == 5)
m.MLectureSlot = textReader2.Value;
if (moduleDetailCounter == 6)
m.MTutorialSlot = textReader2.Value;
// ls_modules_list.Items.Add(reader2.Value);
moduleDetailCounter++;
}
if (moduleDetailCounter == 7) { moduleDetailCounter = 0; break; }
}
modules[modulecounter] = m;
modulecounter++;
}
}
for (int i = 0; i < modules.Length; i++)
{
ModulesListBox.Items.Add(modules[i].MName); // THE ERROR APPEARS HERE
}
}
I'm getting that error on the line which is marked with // THE ERROR APPEARS HERE.
Either ModulesListBox is null because you're accessing it before it is initialized or the modules array contains empty elements.
Like one of the commenters said, you're probably better off using XmlSerializer to handle loading the XML into the collection of modules. If that's not possible, change modules to a List<Modules> instead.
You initialize your modules array to be 16 in length and you load it with the modulecounter, but in the loop use the array length. Instead use the modulecounter variable to limit the loop, like this:
for (int i = 0; i < modulecounter; i++)
{
ModulesListBox.Items.Add(modules[i].MName);
}
Your array is null for every value modulecounter and up. That is why the error.
the for loop runs from 0 to 16 but modules is only 0 to 15, change modules.length to (modules.length -1)
Almost positive the issue is somewhere with your deserialization logic. One could debug it, but why reinvent the wheel?
var serializer = new XmlSerializer(typeof(List<Module>), new XmlRootAttribute("Modules"));
using (var reader = new StreamReader(workingDir + #"\ModuleList.xml"))
var modules = (List<Module>)serializer.Deserialize(reader);
this would give a nice complete collection of Modules assuming it was defined as
public class Module
{
public string MCode;
public string MName;
public int MCapacity;
public int MSemester;
public string MPrerequisite;
public int MLectureSlot;
public int MTutorialSlot;
}
If you have no problems with memory (i.e: the file is usually not too large), then I suggest not to use XmlTextReader and using XmlDocument instead:
XmlDocument d = new XmlDocument();
d.Load(#"FileNameAndDirectory");
XmlNodeList list = d.SelectNodes("/Modules/Module/MName");
foreach (XmlNode node in list)
{
// Whatsoever
}
The code above should extract every MName node for you and put them all in list, use it for good :)

How to set array length in c# dynamically

I am still new to C# and I've been struggling with various issues on arrays. I've got an array of metadata objects (name value pairs) and I would like to know how to create only the number of "InputProperty" objects that I truly need. In this loop I've arbitrarily set the number of elements to 20 and I try to bail out when the entry becomes null but the web service on the receiving end of this does not like any null elements passed to it:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = new InputProperty[20]; // how to make this "dynamic"
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip;
return update;
}
In summary, say I only have 3 namevalue pairs in the above input array? Rather than allocate 20 elements for the array called ip, how can code this so ip is only as big as it needs to be. The update object is passed across another webservice so serialization is important (i.e. I can't use namevaluecollection, etc.).
p.s. Is the only way to followup on a posted question through the "add comments" facility?
InputProperty[] ip = new InputProperty[nvPairs.Length];
Or, you can use a list like so:
List<InputProperty> list = new List<InputProperty>();
InputProperty ip = new (..);
list.Add(ip);
update.items = list.ToArray();
Another thing I'd like to point out, in C# you can delcare your int variable use in a for loop right inside the loop:
for(int i = 0; i<nvPairs.Length;i++
{
.
.
}
And just because I'm in the mood, here's a cleaner way to do this method IMO:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
var ip = new List<InputProperty>();
foreach(var nvPair in nvPairs)
{
if (nvPair == null) break;
var inputProp = new InputProperty
{
Name = "udf:" + nvPair.Name,
Val = nvPair.Value
};
ip.Add(inputProp);
}
update.Items = ip.ToArray();
return update;
}
If you don't want to use a List, ArrayList, or other dynamically-sized collection and then convert to an array (that's the method I'd recommend, by the way), then you'll have to allocate the array to its maximum possible size, keep track of how many items you put in it, and then create a new array with just those items in it:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = new InputProperty[20]; // how to make this "dynamic"
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
if (i < nvPairs.Length)
{
// Create new, smaller, array to hold the items we processed.
update.Items = new InputProperty[i];
Array.Copy(ip, update.Items, i);
}
else
{
update.Items = ip;
}
return update;
}
An alternate method would be to always assign update.Items = ip; and then resize if necessary:
update.Items = ip;
if (i < nvPairs.Length)
{
Array.Resize(update.Items, i);
}
It's less code, but will likely end up doing the same amount of work (i.e. creating a new array and copying the old items).
Use this:
Array.Resize(ref myArr, myArr.Length + 5);
Does is need to be an array? If you use an ArrayList or one of the other objects available in C#, you won't have this limitation to content with. Hashtable, IDictionnary, IList, etc.. all allow a dynamic number of elements.
You could use List inside the method and transform it to an array at the end. But i think if we talk about an max-value of 20, your code is faster.
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
List<InputProperty> ip = new List<InputProperty>();
for (int i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip.ToArray();
return update;
}
Or in C# 3.0 using System.Linq you can skip the intermediate list:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
var ip = from nv in nvPairs
select new InputProperty()
{
Name = "udf:" + nv.Name,
Val = nv.Value
};
update.Items = ip.ToArray();
return update;
}
Use Array.CreateInstance to create an array dynamically.
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = Array.CreateInstance(typeof(InputProperty), nvPairs.Count()) as InputProperty[];
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip;
return update;
}
Typically, arrays require constants to initialize their size. You could sweep over nvPairs once to get the length, then "dynamically" create an array using a variable for length like this.
InputProperty[] ip = (InputProperty[])Array.CreateInstance(typeof(InputProperty), length);
I wouldn't recommend it, though. Just stick with the
List<InputProperty> ip = ...
...
update.Items = ip.ToArray();
solution. It's not that much less performant, and way better looking.
You can create an array dynamically in this way:
static void Main()
{
// Create a string array 2 elements in length:
int arrayLength = 2;
Array dynamicArray = Array.CreateInstance(typeof(int), arrayLength);
dynamicArray.SetValue(234, 0); // → a[0] = 234;
dynamicArray.SetValue(444, 1); // → a[1] = 444;
int number = (int)dynamicArray.GetValue(0); // → number = a[0];
int[] cSharpArray = (int[])dynamicArray;
int s2 = cSharpArray[0];
}

Categories

Resources