I am trying to copy an object onto the windows clipboard and off again. My code is like this:
Copy on to clipboard:
Clipboard.Clear();
DataObject newObject = new DataObject(prompts);
newObject.SetData(myString);
Clipboard.SetDataObject(newObject);
Where prompts is a List<Data.Sources.PromptResult> collection.
Copy off clipboard:
IDataObject dataObject = System.Windows.Forms.Clipboard.GetDataObject();
if (dataObject.GetDataPresent(typeof(List<Data.Sources.PromptResult>)))
{
Type type = typeof(List<Data.Sources.PromptResult>);
Object obj = dataObject.GetData(type);
return (List<Data.Sources.PromptResult>)dataObject.GetData(type);
}
The GetFormats() shows the format as being in the list and the GetDataPresent(List<Data.Sources.PromptResult>) returns true but if I try to get the object out of the Clipboard class with GetData(List<Data.Sources.PromptResult>) I get a return of null.
Does anyone have any idea what might be wrong?
OK I tried to add list of my user type to clipboard and get it back...
Here is what I tried:
My User Class:
public class User
{
public int Age { get; set; }
public string Name { get; set; }
}
Rest of Code:
// Create User list and add some users
List<User> users = new List<User>();
users.Add(new User { age = 15, name = "Peter" });
users.Add(new User { age = 14, name = "John" });
// Lets say its my data format
string format = "MyUserList";
Clipboard.Clear();
// Set data to clipboard
Clipboard.SetData(format, users);
// Get data from clipboard
List<User> result = null;
if (Clipboard.ContainsData(format))
result = (List<User>)Clipboard.GetData(format);
...and result was null :)
...until I marked User class as Serializable
[Serializable]
public class User
{
//...
}
After that my code worked.
Ok its not the answer but maybe it helps you some how.
#Reniuz thanks for your help it has helped me to work out the answer.
In order to get the text and custom object data out of the Clipboard with multiple formats I have implemented the IDataObject interface in my own class. The code to set the data object must have the copy flag set like this:
Clipboard.Clear();
Clipboard.SetDataObject(myClassThatImplementsIDataObject, true);
To get the data out again the standard text can be retrieved using:
Clipboard.GetText();
The data can be retrieved using the data method:
Clipboard.GetData("name of my class");
The other point that was helpful was to test that the object I was putting into the clipboard could be serialized by using the BinaryFormatter class to perform this test... If an exception is thrown that copying to the clipboard would also fail, but silently.
So my class has:
[Serializable]
public class ClipboardPromptsHolder : IDataObject
{
...
}
I had a similar scenario and after marking my class as serializable I got it to work.
So try putting the Serializable attribute on your class Data.Sources.PromptResult.
I found out that, if your class is derived from a different class, the base class also needs to be made [Serializable], otherwise that recipe does not work. In my case, it was something like
public abstract class MyAbstractUser
{
...
}
[Serializable]
public class MyUser : MyAbstractUser
{
...
}
When I tried to exchange values of MyUser over the clipboard, it did not work, but when I added [Serializable] to MyAbstractUser, it did work.
I came across this while trying to send a list of items to my clipboard. What I needed was the string representation of those items, not the entire object. I tried a few of the suggestions here, to no avail. However, I did come up with my own solution and I wanted to share it. See below.
public static void CopyToClipboard<T>(this IEnumerable<T> items)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (T item in items)
stringBuilder.Append(item.ToString()).AppendLine();
Clipboard.SetText(stringBuilder.ToString());
}
Add this as an extension method and be sure to override your custom Type's ToString() method.
Related
I am trying to fill a comboBox and a secondary form from a separate class file and I think I have the basics wrong.
The following is shortened to show the bones of what I have and what I think I may be doing wrong. I am not sure if I should be using List<> to populate the comboBox or an Array and suspect in either case my method declaraction is wrong and I cannot find a reference to the comboBox to populate from within the foreach loop.
OK the program.cs for my settings Form. This is not the main form but is the one I am looking to populate when a user select the comboBox.
Program.cs
class Settings
{
public partial class Settings : Form
{
// a bunch of String declarations used throughout
public Settings()
{
InitializeComponent();
}
}
}
The method within a class in a separate file is
Functions.cs
class functions
//This is a separate class to the Settings Form but same namespace.
{
//some global variables here
public string getDomain(string webURL)
{
//more variable declarations
//webURL is a value from the Settings Form
//code to send query to website, get the response and filter the response.
//This is the response filter
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:domains/DAV:domain", nsmgr))
{
strNode = node.InnerText;
responseString += strNode + " ";
list.Add(strNode);
//I would like to simply Add.Items(strNode) to the Settings.Form.cbxDomains but not as simple as this.
}
//this returns all the correct information as a space separated string.
return responseString;
}
}
From Update UI from a different thread in a different class
I thinks there are 2 things I should do.
1. change the form initialisation from InitilizeComponents() to
settingsWindow = new MyForm();
Application.Run(form);
Then simply call Settings.settingsWindow.cbxDomain.Add>items(responseString);
But do I also need to change the actual method to something like
public void List<String> getDomain(string webURL)
I am so confused. Most examples show it the other way of updating the class from the combo not the other way and some say create it as an array.
I actually think it could even be trimmed down further into one or 2 lines instead of the foreach, but that is way beyond my skillset at this time.
Here is how you could populate your combobox from another form.
I will use example since I do not know you code structure but you will get the point.
public class User //Custom generic class
{
public int _Id { get; set; }
public string _Name { get; set; }
}
public class Functions
{
public static List<User> PopulateComboboxWithUsers()
{
List<User> list = new List<User>();
foreach(var something in somethingBig) //You can change this if you re reading form XML with it's variables or something else
{
list.Add(new User { _Id = something.Id, _Name = something.Name };
}
return list;
}
}
public class Settings
{
public Settings()
{
InitializeComponents();
comboBox1.DataSource = Functions.PopulateComboboxWithUser();
comboBox1.DisplayMember = "_Name";
comboBox1.ValueMember = "_Id";
}
}
Other approach is to do the same function expect you will pass comboBox to it and you will do assigning inside it but I think this is more flexible.
I am trying to use the Highrise Api with the .NET 4.5 wrapper by scottschluer in order to post a new Person to our Highrise account along with any custom data that they enter.
The issue that I am running into is with the custom data. The person object gets posted just fine, but the subject data fields are not being included with the post.
I did come across this post: Highrise Custom Fields. It looked like the thing that was missing was the type="array" attribute from the subject_datas field. I tested this hypothesis by manually creating a request using the serialized value of the object I was trying to post. The result of this test, was a successful post to the server with all custom data fields filled.
I've tried extending the classes from the wrapper assembly to add that missing attribute to the list, but that still didn't seem to work. The Person object has a property for a List of SubjectData objects, I overwrote that property in a child class to use a custom class instead. This way I could add a property to use as the attribute. This custom class still didn't seem to work.
After looking at the code for RestSharp's XmlSerializer, it appears that it will only add a list of items when that object implements IList. That wasn't an issue, i was able to get that working, but the code does not seem to allow for adding attributes to the list element. It only looks at the children of the list class and ignores any other properties on the object.
So my question is this:
Is it possible to apply attributes to a list property in RestSharp, or is there another way to add the type="array" attribute to the data_subjects xml node before the request is sent?
I eventually solved the problem myself by creating a new request class that would create a RestRequest using a custom XmlSerializer.
I then extended the Person class and hid the property behind a custom list object property
Before:
[SerializeAs(Name = "subject_datas")]
public List<SubjectData> SubjectDatas { get; set; }
After:
[SerializeAs(Name = "subject_datas")]
public new SubjectDataList SubjectDatas { get; set; }
The SubjectDataList class is just a wrapper for List<SubjectData>.
SubjectDataList implements an interface called ISerializeList<SubjectData>
which is defined as:
interface ISerializeList : IEnumerable {}
interface ISerializeList<T> :IEnumerable<T>, ISerializeList {}
SubjectDataList also has a type property to render the type attribute onto the subjectdatas node of the rest request.
[SerializeAs(Name = "type", Attribute = true)]
public string Type { get; set; } = "array";
I then made a class called XmlListSerializer which implements ISerializer. I copied the implementation of XmlSerializer, but i made a few modifications. In the Map method, there is a part that checks if the variable rawValue is an IList. I changed this part slightly, and added a clause for my XmlListSerializer class.
So it now looks like this:
if (propType.IsPrimitive || propType.IsValueType || propType == typeof(string)) {
//...
} else if (rawValue is IList) {
ProcessIList((IList) rawValue, element);
} else if (rawValue is ISerializeList) {
ProcessISerializeList((ISerializeList) rawValue, element);
} else {
Map(element, rawValue);
}
Where ProcessIList and ProcessISerializeList are defined as:
private void ProcessIList(IList list, XElement element) {
ProcessIEnumerable(list, element);
}
private void ProcessISerializeList(ISerializeList list, XElement element) {
ProcessIEnumerable(list, element);
Map(element, list);
}
private void ProcessIEnumerable(IEnumerable list, XElement element) {
var itemTypeName = "";
foreach (var item in list) {
if (itemTypeName == "") {
var type = item.GetType();
var setting = type.GetAttribute<SerializeAsAttribute>();
itemTypeName = setting != null && setting.Name.HasValue() ? setting.Name : type.Name;
}
var instance = new XElement(itemTypeName.AsNamespaced(Namespace));
Map(instance, item);
element.Add(instance);
}
}
I hope this answer will be able to help anyone else having issues with this problem.
I'm only using Code Analysis for cleaning, organizing and ensuring these changes are globally performed for all instances of a particular warning. I'm down to the final, and it's CA2227.
CA2227 Collection properties should be read only Change '' to be
read-only by removing the property setter.
Note this is for mapping of EDI documents. These classes are to represent a whole or part of an EDI document.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; set; }
}
You can see all of the Collection properties will give me this warning, and there are hundreds of them. When using the above class I instantiate it without any data. Then externally I add the data and set each individual variable through its public accessor. I do not instantiate this class with all the data prepared and passed using a constructor method (IMO for the size these can reach it can easily wreak havoc on the eyes). When complete and all properties are assigned the class as a whole is then used to generate that part of a document it represents.
My question is, for the usage described above, what would be a better approach for setting this up correctly? Do I keep the public accessors and suppress this warning entirely, or is there a entirely different solution that would work?
Here's what MSDN says about the error, and also how you can avoid it.
Here's my take on the issue.
Consider, the following class:
class BigDataClass
{
public List<string> Data { get; set; }
}
This class will throw that exact same issue. Why? Because Collections do not need a setter. Now, we can do anything with that object: assign Data to an arbitrary List<string>, add elements to Data, remove elements from Data, etc. If we remove the setter, we only lose the ability to directly assign to that property.
Consider the following code:
class BigDataClass
{
private List<string> data = new List<string>();
public List<string> Data { get { return data; } } // note, we removed the setter
}
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
This code is perfectly valid and in fact the recommended way to do things. Why? Because the List<string> is a reference to a memory location, that contains the remainder of the data.
Now, the only thing you cannot now do with this, is directly set the Data property. I.e. the following is invalid:
var bigData = new BigDataClass();
bigData.Data = new List<string>();
This is not necessarily a bad thing. You'll notice that on many .NET types this model is used. It's the basics of immutability. You usually do not want direct access to the mutability of Collections, as this can cause some accidental behavior that has strange issues. This is why Microsoft recommends you omit setters.
Example:
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
var l2 = new List<string>();
l2.Add("String 1");
l2.Add("String 2");
bigData.Data = l2;
Console.WriteLine(bigData.Data[0]);
We might be expecting Some String, but we'll get String 1. This also means that you cannot reliably attach events to the Collection in question, so you cannot reliably determine if new values are added or values are removed.
A writable collection property allows a user to replace the collection with a completely different collection.
Essentially, if you only ever need to run the constructor, or assignment, once, then omit the set modifier. You won't need it, direct assignment of collections is against best-practices.
Now, I'm not saying never use a setter on a Collection, sometimes you may need one, but in general you should not use them.
You can always use .AddRange, .Clone, etc. on the Collections, you only lose the ability of direct assignment.
Serialization
Lastly, what do we do if we wish to Serialize or Deserialize a class that contains our Collection without a set? Well, there is always more than one way to do it, the simplest (in my opinion) is to create a property that represents the serialized collection.
Take our BigDataClass for example. If we wished to Serialize, and then Deserialize this class with the following code, the Data property would have no elements.
JavaScriptSerializer jss = new JavaScriptSerializer();
BigDataClass bdc = new BigDataClass();
bdc.Data.Add("Test String");
string serd = jss.Serialize(bdc);
Console.WriteLine(serd);
BigDataClass bdc2 = jss.Deserialize<BigDataClass>(serd);
So, to fix this, we can simply modify our BigDataClass a bit to make it use a new string property for Serialization purposes.
public class BigDataClass
{
private List<string> data = new List<string>();
[ScriptIgnore]
public List<string> Data { get { return data; } } // note, we removed the setter
public string SerializedData { get { JavaScriptSerializer jss = new JavaScriptSerializer(); return jss.Serialize(data); } set { JavaScriptSerializer jss = new JavaScriptSerializer(); data = jss.Deserialize<List<string>>(value); } }
}
Another option is always the DataContractSerializer (which is really a better option, in general.) You can find information about it on this StackOverflow question.
With current VS2019 we can simply do this:
public List<string> Data { get; } = new List<string>();
This satisfies CA2227 and can be serialized/deserialized.
The deserialization works because List<> has an "Add" method, and the serializer knows how to handle a read-only collection property with an Add method (the property is read-only but not the elements) (I use Json.Net, other serializers may behave differently).
Edit:
As pointed out it should be "=" and not "=>" (compiler will prevent you using "=>"). If we used "public List Data => new List();" then it would create a new list every time the property was accessed which is not what we want either.
Edit:
Note that this will NOT work if the type of the property is an interface, such as IList
Edit:
I think the handling of interfaces is determined by the serializer used. The following works perfectly. I'm sure all common serializers know how to handle ICollection. And if you have some custom interface that does not implement ICollection then you should be able to configure the serializer to handle it, but in that case CA2227 probably won't be triggered making it irrelevant here. (As it is a read-only property you have to assign a concrete value within the class so it should always be serializing and de-serializing a non-null value)
public class CA2227TestClass
{
public IList Data { get; } = new List<string>();
}
[TestMethod]
public void CA2227_Serialization()
{
var test = new CA2227TestClass()
{
Data = { "One", "Two", "Three" }
};
var json = JsonConvert.SerializeObject(test);
Assert.AreEqual("{\"Data\":[\"One\",\"Two\",\"Three\"]}", json);
var jsonObject = JsonConvert.DeserializeObject(json, typeof(CA2227TestClass)) as CA2227TestClass;
Assert.IsNotNull(jsonObject);
Assert.AreEqual(3, jsonObject.Data.Count);
Assert.AreEqual("One", jsonObject.Data[0]);
Assert.AreEqual("Two", jsonObject.Data[1]);
Assert.AreEqual("Three", jsonObject.Data[2]);
Assert.AreEqual(typeof(List<string>), jsonObject.Data.GetType());
}
💡 Alternative Solution 💡
In my situation, making the property read-only was not viable because the whole list (as a reference) could change to a new list.
I was able to resolve this warning by changing the properties' setter scope to be internal.
public List<Batch> Batches
{
get { return _Batches; }
internal set { _Batches = value; OnPropertyChanged(nameof(Batches)); }
}
Note one could also use private set...
The hint's (achilleas heal) of this warning seems really pointed to libraries for the documentation says (Bolding mine):
An externally visible writable property is a type that implements
System.Collections.ICollection.
For me it was, "Ok, I won't make it viewable externally...." and internal was fine for the app.
Thanks to #Matthew, #CraigW and #EBrown for helping me understanding the solution for this warning.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; private set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; private set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; private set; }
public PO1Loop()
{
PIDRepeat1 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID1>();
PIDRepeat2 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID2>();
ACKRepeat = new Collection<SegmentTypes.PO1LoopSegmentTypes.ACK>();
}
}
When wanting to assign data to the collection types use AddRange, Clear or any other variation of method for modifying a collection.
Only while binding DTO, you need to suppress warnings.
otherwise a custom ModelBinder is required custom ModelBinder to bind collections.
quoting the rule documentation:
When to suppress warnings
You can suppress the warning if the property is part of a Data Transfer Object (DTO) class.
Otherwise, do not suppress warnings from this rule.
https://learn.microsoft.com/pt-br/visualstudio/code-quality/ca2227?view=vs-2019
DTOs often require serialization and deserialization. Thus, they are required to be mutable.
Having to create an alternate backing property is a pain.
Simply change the property type from List<string> to IReadOnlyList<string> then this works as expected without CA2227.
The collection is set via the property but you can also cast to List<string> if you wish to append or delete items.
class Holder
{
public IReadOnlyList<string> Col { get; set; } = new List<string>();
}
var list = new List<string> { "One", "Two" };
var holder = new Holder() { Col = list } ;
var json = JsonConvert.SerializeObject(holder);
// output json {"Col":["One","Two"]}
var deserializedHolder = JsonConvert.DeserializeObject<Holder>(json);
I had to fix some of the CA2227 violations, so i had to add the "readonly" keyword to the collection field and then of course, had to remove the setter property. Some code that have used the setter, just created a new collection object which initially was empty. This code sure did not compile so i had to add a SetXxx() method in order to realize the missing setter's functionality. I did it like this:
public void SetXxx(List<string> list)
{
this.theList.Clear();
this.theList.AddRange(list);
}
The code of callers using the setter has been replaced with a call to the method SetXxx().
Instead of creating a complete new list, the existing list now will be cleared and filled with new items from another list, passed in as a parameter. The original list, due to the fact it is readonly and created only once, will always remain.
I believe this is also a good way to avoid that the garbagae collector has to delete old objects that got out of scope and second, to create new collection objects although there is already one.
As an addition to Der Kommissar's excellent answer.
Starting with .NET 5 (C# 9.0) there are init-only properties. These properties are only settable under specific circumstances, see here for reference.
The following example should not raise a warning CA2227, yet still allow for the collection being set during object initialization.
using System.Collections.Generic;
namespace BookStore
{
public class BookModel
{
public ICollection<string> Chapters { get; init; }
}
}
Note that the current version of the .NET SDK still raises a warning when using the built-in analyzer (not the NuGet package). This is a known bug and should be fixed in the future.
To cover all the possible scenarios to resolve CA2227 error:
This covers the Entity relationship mapping when we use Entity Framework.
class Program
{
static void Main(string[] args)
{
ParentClass obj = new ParentClass();
obj.ChildDetails.Clear();
obj.ChildDetails.AddRange();
obj.LstNames.Clear();
obj.LstNames.AddRange();
}
}
public class ChildClass
{ }
public class ParentClass
{
private readonly ICollection<ChildClass> _ChildClass;
public ParentClass()
{
_ChildClass = new HashSet<ChildClass>();
}
public virtual ICollection<ChildClass> ChildDetails => _ChildClass;
public IList<string> LstNames => new List<string>();
}
I hope you can help out a fellow programmer. Basically, I want the user input from the Rich Text Box (taskNameRTB) to be assigned to the taskName; string variable in my class taskStructure which is in form1 shown below:
public partial class Form1 : Form
{
public class taskStructure
{
public string taskName;
public string taskDescription;
public int Priority;
public string dateAndTime;
}
public List<taskStructure> TasksArray = new List<taskStructure>(); //Declared a list data structure
In my second form which is where the user enters everything related to the task, I want to send this information to the list after the 'Create Task' button has been clicked:
private void createTaskBtn_Click(object sender, EventArgs e)
{
Form1 welcomeForm = new Form1();
welcomeForm.TasksArray[0].taskName = taskNameRTB.Text;
welcomeForm.TasksArray[0].taskDescription = taskDescRTB.Text;
}
However, when I do this I get a ArgumentOutOfRangeException and I do not understand why. I have also tried these:
welcomeForm.TasksArray[0].Add(taskDescRTB.Text);
welcomeForm.TasksArray.Insert(0, taskNameRTB.Text);
welcomeForm.TasksArray.Add(taskDescRTB.Text);
taskNameRTB.Text = welcomeForm.TasksArray[0].taskName;
But the ones that run come up with the same error ArgumentOutOfRangeException and some of them don't work, such as:
welcomeForm.TasksArray[0].Add(taskDescRTB.Text);
I'm aware that the list has not been initialized, but how can I initialize it when it doesn't allow me to initialize it with user input...
Any light you can shed on this will be really helpful
Kind Regards,
Kieran
You need to add a new taskStructrue to the list.
welcomeForm.TasksArray.Add(new taskStructure
{
taskName = taskDescRTB.Text,
taskDescription = taskDescRTB.Text
});
But personally I'd rewrite that class to follow naming conventions and to use properties instead of public fields.
public class TaskStructure
{
public string TaskName { get; set; }
public string TaskDescription { get; set; }
public int Priority { get; set; }
public string DateAndTime { get; set; }
}
have you tried
welcomeForm.TasksArray.Add(new taskStructure(taskDescRTB.Text));
I don't know what taskStructure is, but you need to fill TasksArray with types of it.
Your TaskStructure is a class, and you are putting all TaskStructure objects into a list,
public List<taskStructure> TasksArray = new List<taskStructure>(); //Declared a list data structure
Does your Form1() have a constructor that calls InitializeComponents()?
If so, you could try adding TasksArray = new List<taskStructure>() right below InitializeComponents(), because it looks like you're trying to access the list data structure that hasn't been initialized with new.
Alternatively
As another user noted, you can create a constructor class for TaskStructure like this:
public TaskStructure(RTB rtb1, rtb2, rtb3) //where RTB is the rich text box type
{
taskName = rtb1.text;
taskDescription = rtb2.text;
//and so on.
}
Then you can do TaskArray.add(new TaskStruture(rtb1,rtb2,rtb3).
Thrid Edit
Just realized your TaskArray is actually a List, which in C# (and Java), you cannot access it with an index like TaskArray[0], you have to use getter and setter methods, which in this case is TaskArray.add(), and TaskArray.get(0), you're getting ArgumentOutOfRangeException because you're trying to access a List using square indexes like this --> [0]. You can actually access a list doing list1, as pointed out by another user.
Here's a good tutorial on C# lists, by DotNetPerls
I'm making a program that catalogs the names of the people I add in a listbox and save all the content to a file when you close the program.
Each line of the file is for a person:
Tommy
Marco
James
Dylan
When the program starts, the file data is loaded and add the names to listbox.
All this works great, but now I'm having trouble making something else.
Each person on the list need a variable to indicate whether it has paid and I want to save this variable together with his name on the file.
For this, I have:
bool paid;
private void checkbox_Paid_CheckedChanged(object sender, EventArgs e)
{
paid = true;
}
I have only one checkbox, and it needs to differ from each person in the listbox, according to the selected in the event:
private void listDOF_SelectedIndexChanged(object sender, EventArgs e)
{
}
But I do not know how to write this bool in the file for each person, save to the file together with name and load it.
I've tried something like:
foreach(string purchasers in listDOF.Items)
{
arq.WriteLine(purchases, paid);
}
Obviously this do not worked, I do not know how to assign the bool for each one of the purchasers and write it in the file.
I'm using .NET 4.5 in a WFA.
Thanks all in advance, if I able to do this, I will give a big step toward the knowledge.
First of all I would store the persons in a list in the application otherwise you will only have one bool for all the persons in the application and everyone will be set as payed or not payed.
public class Person
{
public string Name { get; set; }
public bool Payed { get; set; }
}
public List<Person> Persons { get; set; }
As simple way is to save it in a SCV file. Semicolons between fields and new row per post
Tommy; true
Marco; true
Then save like this
foreach(var person in Persons)
{
arq.WrtieLine("{0};{1}", person.Name, person.Payed);
}
I believe that this is a good case to turn your text file into JSON.
For example:
[
{ name: "Tom", hasPaid: true },
{ name: "Matias", hasPaid: false } // Urgh!
]
And In C# you can build up this JSON using anonymous objects:
List<object> people = new List<object>();
people.Add(new { Name = "Tom", HasPaid = true });
Finally, you can serialize to JSON using JSON.NET:
File.WriteAllText(#"C:\myfile.json", JsonConvert.SerializeObject(people));
...or deserialize it:
List<object> people = JsonConvert.DeserializeObject<List<object>>(File.ReadAllText(#"C:\myfile.json"));
Update
Maybe you should use an concrete class rather than an anoymous object, because the later is inmutable and you're going to get in troubles if you need to data-bind the object list to the whole ListBox.
You can declare a simple class like this:
public class Person
{
public string Name { get; set; }
public bool HasPaid { get; set; }
}
...add persons this way:
List<Person> people = new List<Person>();
Person person = new Person();
person.Name = "Tom";
person.HasPaid = true;
people.Add(person);
File.WriteAllText(#"C:\myfile.json", JsonConvert.SerializeObject(people));
And when it comes to deserializing the whole JSON array:
List<Person> people = JsonConvert.DeserializeObject<List<Person>(File.ReadAllText(#"C:\myfile.json"));
Update 2
It seems like you don't know you can bind a list to Windows Forms controls. See for example the following MSDN article.
Instead of List<T> you can use BindingList<T>. Check this CodeProject guide.
This way, when you add an item to the whole BindingList<T> the ListBox control will be automatically populated. If you remove one, it will also dissapear from the UI.
Both Windows Forms and Windows Presentation Foundation are powerful and productive if you leverage data-binding (also in Web development, say KnockoutJS!).