Can't display 2 different objects on 2 different listboxes - c#

To clarify before hand this is for an assignment if what I am doing is a bad way i apologize but its all part of the criteria.
Having an issue with WinForms being able to handle having 2 listBoxes display 2 different objects data. I can successfully add data to either one of the listBoxes without and issue but as soon as i try and add data to the other, i am met with this error.
System.NullReferenceException: 'Object reference not set to an instance of an object.'
c was null.
Photo of Gui
private void DisplayBook()//display method
{
//current index is equal to 0 and increments everytime an item is added ( max of 20)
for (int i = 0; i < currentIndex; i++)
{
//Book is a class with getters and setters, myBooks is an array
Book b = myBooks[i];
//printbook is a print method on the Book class
lstbook.Items.Add(b.Printbook());
}
}
private void DisplayCustomer()//display method
{
lstcustomer.Items.Clear();
for (int i = 0; i < currentIndex; i++)
{
Customer c = myCustomer[i];
lstcustomer.Items.Add(c.Printcustomer());
}
}
example of my two display methods for each listBox.
I have used break points to try and determine the issue but it has been quite elusive, only throwing the above exception on these two lines.
Customer c = myCustomer[i];
Book b = myBooks[i];

You seem to be using the same counter (currentIndex) for both Customer and Book. So when you first add a customer and then a boox, the counter will be 2 but there's only one item in each list.
To make it easier to display the objects, add overrides for the ToStringmethod on both your objects, because then we can add the entire object to the listbox:
public override string ToString() {
return this.PrintBook(); // or this.PrintCustomer();
}
You could store the objects in a list instead, then you wouldn't have to use a counter:
IList<Customer> customers = new List<Customer>();
private void AddCustomer(Customer customer) {
customers.add(customer);
}
We could then use a foreach loop to add all items to the listbox, but since we already have all items in a list, we can set that as DataSource for the listbox:
lstcustomer.DataSource = customers;

Related

How to "return" multiple times with for loop?

Hopefully this post gives more clarity as to what I am trying to achieve.
Objective: I want to spawn 20 apples(that have an attached button) from a list at runtime. When the apples are clicked they will spawn a popup with information pertaining to the apple that was clicked.
What I'm doing currently: I am using a for loop to run through the list to spawn the apples. I currently have the following code:
public class AppleInventory : MonoBehaviour
{
[SerializeField] private ApplesScript applPrefab;
[SerializeField] private Transform applParent;
public ApplesScript CreateApples()
{
var appl = Instantiate(applPrefab, applParent);
for (int i = 0; i < apples.Count; i++)
{
appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
return appl;
}
}
The Problem: The problem is that when I use the for loop and click on the button,it returns the following error: ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. The popup information also does not update.
Code without for loop: The code works to spawn one apple when I remove the for loop and set the int i = to a specific number, like below. It will give the correct popup info for any number that "i" is set to. This lets me know that it is not the rest of the code that is the issue. This leads me to believe it is the "return" line along with the for loop that is the issue. It seems I may need to "return" for each iteration but I am unsure of how to go about doing this.
public ApplesScript CreateApples()
{
int i = 7;
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
return appl;
}
Thank you,
-
UPDATE
The fix was so simple. I just ended up creating a new method specifically for the for loop and it worked the way I wanted. My code now looks like this:
public void StarterOfApplesCreation()
{
for (int i = 0; i < apples.Count; i++)
{
CreateApples(i);
}
}
public void CreateApples(int i)
{
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
You have two options. The conventional option is to create all the items first and then return them all in some sort of list, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static Thing[] GetThings(int count)
{
var things = new Thing[count];
for (var i = 0; i < count; i++)
{
things[i] = new Thing { Number = i };
}
return things;
}
The more modern option is to use an iterator. It actually will return one item at a time. It has the limitation that you have to use the items there and then - you won't have random access like you would an array or the like - but it also has advantages, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static IEnumerable<Thing> GetThings(int count)
{
for (var i = 0; i < count; i++)
{
var thing = new Thing { Number = i };
yield return thing;
}
}
The result of an iterator will usually be used as the source for a foreach loop or a LINQ query. Note that you can always call ToArray or ToList on the result of an iterator if you do want random access in specific situations, but you still have the advantages of an iterator elsewhere. For instance, let's say that your method produces 1000 items and you want to find the first one that matches some condition. Using my first example, you would have to create all 1000 items every time, even if the first one was a match. Using an iterator, because the items are processed as they are created, you can abort the process as soon as you find a match, meaning that you won't unnecessarily create the remaining items.
Note that my examples use the following class:
public class Thing
{
public int Number { get; set; }
}
You can copy and paste the code into a Console app that doesn't use top-level statements. The bones of the code will still work with top-level statements, but you'll need to make a few other modifications.
Store each separate "appl" that gets instantiated in an Array, ie appls[i]=appl
Do this within the for loop.
If you think about it, by putting the line "return appl;" outside the for loop, you are only storing that last game object, not all of them. Thats why creating an array of gameobjects and assigning them within the loop may work for you.

How can i read cell value in C# from DataGridView

im trying to do a StackAutomata for a School assigment and I try find the cells via for but they cannot read it
int i, K and J is created outside the do {} while cicle
for (int j = 0; j < gv1.Columns.Count; j++)
{
if (input[i].ToString() == gv1.Rows[0].Cells[j].Value)
{
K = j;
}
}
J = K;
string a = verem.Pop();
for (int j = 0; j < gv1.Rows.Count; j++)
{
if (a == gv1.Rows[j].Cells[0].Value)
{
K = j;
}
}
What am i missing?
It is seldom a good idea to directly read and write to the DataGridView yourself. It is better to separate your model from the way that your model is displayed.
If your data can exist without its display, it will be easier to change the way you display it, it will be easier to reuse the data in another control, or different format. Easier to unit test the data handling (no form needed!), to change the internal layout of the data without having to change those who display this data.
This separation between View and Model is implemented in extrema in the MVVM model, but windows Forms also supports this via Data Binding.
Whenever you have a control that displays information about a sequence of similar items, whether it is a ListBox, a DataGridView or even a GraphView, you'll find that it has a property DataSource.
All you have to do is put the data that you want to show in the DataSource. Other properties define how the data in the DataSource has to be displayed. If the operator changes the displayed data, then the original data is automatically updated.
Suppose your DataGridView has to show a collection of Customers: one Customer per row.
Your DataGridView has several DataGridViewColumns. Every column will show the value of one of the properties. The name of the property to show is in property DataGridViewColumn.DataPropertyName.
All you have to do is make a sequence of similar items, and assign this to DataGridView.DataSource.
An example will help.
Suppose you need to show a sequence of Students:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime BirthDay {get; set;}
...
}
You want to show these three Student properties in columns. Using the visual studio designer you add the columns and assign the DataPropertyName. The results can be found in InitializeComponents, or you can do it yourself:
private readonly DataGridViewColumn columnId = new DataGridViewColumn();
private readonly DataGridViewColumn columnName = new DataGridViewColumn();
private readonly DataGridViewColumn columnBirthDay = new DataGridViewColumn();
// constructor
public MyForm()
{
InitializeComponents();
// define which column should display which Student property
this.columnId.DataPropertyName = nameof(Student.Id);
this.columnName.DataPropertyName = nameof(Student.Name);
this.columnBirthDay.DataPropertyName = nameof(Student.BirthDay);
// the the DataGridView to use these columns:
this.dataGridView1.Columns.Add(this.columnId);
this.dataGridView1.Columns.Add(this.columnName);
this.dataGridView1.Columns.Add(this.columnBirthday);
}
Usually you set other column properties: you set the text of the Header, the column order, sometimes you define the display format, for instance: for the Birthday you don't want to show the time of day. But if you want, you can make column Id readonly, with a bold font, etc.
Notice, that until now we have done everything without one actual Student.
If you want to display the Students but don't want to edit them, it is enough to put them in a Collection and assign this collection to the DataSource of the DataGridView
List<Student> students = ...
this.DataGridView1.DataSource = students;
However, if you want that operators can Add / Remove / Change / Reorder Students, you need a mechanism that the changes are updated in your students collection. This is done automatically if you put your data in a BindingList
IList<Student> students = ...
BindingList<Student> displayedStudents = new BindingList<Student>(students);
this.DataGridView1.DataSource = displayedStudents;
All students are displayed. The operator can Add / Remove / Change the Students as he likes (apart from the columns that you made readonly). All changes are automatically updated in the BindingList.
You can subscribe to events off the BndingList to react on these changes, but a more common scenario is that you do nothing until the operator tells you that he finished changing, for instance by pressing a button:
private void ButtonOk_Clicked(object sender, ...)
{
DataGridView dataGridView = (DataGridView)sender;
BindingList<Student> students = (BindingList<Student>)dataGridView.DataSource;
foreach (Student student in students)
{
this.ProcessStudent(student);
}
If you have saved copies of the original students, you can detect which Student are changed, added, removed and decide what to do with them.
If you need to do something with the selected rows:
IEnumerable<Student> selectedStudents = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Student>();
One nice feature: suppose you want that your software assigns the Id of the Students, instead of the operator. Then whenever the operator wants to Add a new Row, you have to make the Student, with a unique Id. This is done by handling event BindingList.AddingNew
private void StudentCollection_AddingNew(object sender, AddingNewEventArgs e)
{
// You need to make a Student with a unique Id:
int newStudentId = this.CreateUniqueStudentId();
e.NewObject = new Student
{
Id = newStudentId,
// the operator has to fill in the rest of the properties
}
}
Did you notice that you don't have to care about how the Students are displayed: you don't care which properties, what display format or column order. If in future someone decides not to show the Birthday of the Student anymore, or reorder the columns, maybe change show the name of the Student in capital letters: all these methods will still work.
for (int j = 0; j < gv1.Columns.Count; j++) {
if (input[i].ToString() == gv1.Rows[0].Cells[j].Value.ToString()) {
K = j;
}
}
J = K;
string a = verem.Pop();
for (int j = 0; j < gv1.Rows.Count; j++) {
if (a == gv1.Rows[j].Cells[0].Value.ToString()) {
K = j;
}
}
You cant compare gridview.Rows.Cells.Value with a string as it is an Object, just add the .ToString() Method and it should work.
Also please find a better name for your variables it makes your code more readable.

Does expanding arraylist of objects make a new object?

Assume we have an array list of type Employe , does expanding it's length by 1 make a new object in the list ?
is the code in else statement correct? and is it recommended?
public void ModifierEmp(int c)
{
for(int i = 0; i < Ann.Count; i++)
{
if(Ann[i].Code == c)
{
Ann[i].saisie();
} else
{
i = Ann.Count + 1; //expanding arraylist ann
Ann[i].saisie(); //saisie a method for the user to input Employe infos
}
}
}
https://imgur.com/VfFHDKu "code snippet"
i = Ann.Count + 1;
The code above is not expanding the list: it is only setting your index variable (i) to have a new value.
If you wanted to make the list bigger, you would have to tell it which object to put into that new space you create. For example:
Ann.Add(anotherItem);
Of course, this gives you the ability to decide whether to add an existing item, create a new item (e.g. Ann.Add(new Something() { Code = c })), or even add a null value to the list (which is not usually a good idea).

Create instance of object every nth time through a loop

Apologies if I'm struggling to word this properly. OOP is not my expertise but I'm very much trying to learn.
How do I create an instance of an object on say, every third iteration of a loop?
Within a loop, I need to assign values to an object, but the property to assign a value to will depend on the result of a case statement. Once each property of the object has been assigned, I then need to add that object to a list of objects of the same type.
If I create the object before the loop is entered, then my list just contains the same result over and over again, because (I've read) that the list only contains a reference to the object, and if the object is then changed, so does the list.
If I create the object within the loop, then obviously, I'll get a new object each time with just one of the properties assigned to it by the time it adds it to the list. The list would contain different results, but only the last property would be assigned, as a new object is created each time.
What I assumed you could therefore do was create a new object whenever all of the properties had a value assigned to it (or at the start, when none had). So, since my object has three properties, each time through the loop, I would like to add a new object whenever an int iCounter was 0, add the values, and increment iCounter, then when iCounter is 3, set to 0. However, when I attempt to create an object inside of an if statement, the rest of the program doesn't see the object exists.
I also assumed, I could maybe attempt some kind of macro substitution, which is what I would normally resort to in Fox, however, (I've read) that this is a big no-no in c#.
Any ideas?
try
{
cProducts Product = new cProducts();
SqlConn2.Open();
rdr2 = SqlComm2.ExecuteReader();
int iScanLine = 0;
while (rdr2.Read())
{
iScanLine++;
Product.product = rdr2["product"].ToString();
Product.sOrder = rdr2["order_id"].ToString();
switch (rdr2["detail"].ToString())
{
case "Quantity":
Product.quantity = Convert.ToInt16(rdr2["display_value"]) ;
break;
case "Option":
Product.Option = rdr2["display_value"].ToString();
break;
case "Size":
Product.Size = rdr2["display_value"].ToString();
break;
}
if (iScanLine == 3)
{
lProducts.Add(Product);
thisPage.sProducts.Add(lProducts[lProducts.Count() - 1]);
iScanLine = 0;
}
}
}
You could just change this bit:
if (iScanLine == 3)
{
lProducts.Add(Product);
thisPage.sProducts.Add(Product); //<-- We know the object just added is still in Product
iScanLine = 0;
Product = new cProducts(); //<-- Create a new object to start populating
}
Also, I know that .NET framework is quite new, being only a decade old, but you might consider reading the Naming Guidelines:
X DO NOT use Hungarian notation.
Looks like you have table with four columns, where each product represented in three consecutive rows
product | order_id | detail | display_value
A X Quantity 5
A X Option Foo
A X Size XL
B X Quantity 2
...
And you are trying to read products. I suggest you to store current product name and compare it with last product name. If name is changed, then you are reading data of next product, thus you can create new product and add it to list of products:
IDataReader reader = SqlComm2.ExecuteReader();
List<Product> products = new List<Product>();
Product product = null;
while (reader.Read())
{
var name = reader["product"].ToString();
if (product == null || product.Name != name) // check if new product
{
product = new Product(); // create new product
product.Name = name; // fill name
product.OrderId = reader["order_id"].ToString(); // and order
products.Add(product); // add to products
}
object value = reader["display_value"]; // get value from row
switch (reader["detail"].ToString())
{
case "Quantity":
product.Quantity = Convert.ToInt16(value);
break;
case "Option":
product.Option = value.ToString();
break;
case "Size":
product.Size = value.ToString();
break;
}
}
As you can see, I also refactored naming - PascalCase for properties, camelCase for local variables, no Hungarian notation. Also new names for properties introduced - Product.Name instead of odd Product.Product, OrderId instead of sOrder.
Use the Modulus operator to check whether the iterating variable is divisible by expected nth value or not
if(value % 3 == 0)
{
//do stuff
}
value++;
You are repeatedly adding the same Product to your list, and never create a new one. When you get to the end of your loop, it'll appear as though you've only got a single item in there.
After you've added your item (within the if (iScanLine == 3)), I suspect you want to create a new item: Product = new cProducts().
Also, I would like to reference this particular comment that you make in your question:
If I create the object before the loop is entered, then my list just
contains the same result over and over again, because (I've read) that
the list only contains a reference to the object, and if the object is
then changed, so does the list.
The following code will result in 5 separate objects being added to a list:
List<cProducts> list = new List<cProducts>();
cProducts Product = new cProducts();
for (int i = 0; i < 5; i++)
{
list.Add(Product);
Product = new cProducts();
}
You are correct that the list only contains references to the objects - but you are not changing any of the objects; you are creating new ones. This is a fundamental programming principle, and I'd suggest you take the time to understand how it works before moving on.
Not sure if I understand completely but the following loop whould use a counter to execute every third time
int isThirdTime = 0; //Test condition for third time equality
while (true) //neverending loop
{
if (isThirdTime == 3)//test for 3rd time
{
// add to list
isThirdTime = 0; //reset counter
}
isThirdTime++; // Increase the counter
}

Checking for double booking in arrays

Ok, my project to create an event organizer for a music contest. Basically I take input data such as a list of about 400 entries and I output into a few separate excel sheets by room number and type. The problem is, when assigning events to an array, (I have a separate array for each room and all the arrays are contained in a list) I need to check and fix if accompanists are double booked for the same time slot. Or essentially, if two different arrays have the same accompanist value for array[i]
Ideas? Please try to explain well, I'm self taught with only a bit of experience
private void assignEvents()
{
// assign every event in order
// check accompanists for no double-books
foreach (Event[] room in roomList)
{
int i = 0;
foreach (Event ev in eventList)
{
if (ev.Type == room[0].Type)
{
room[i] = ev;
i++;
}
}
}
You could do it with LINQ like Tejs suggests, but I would go another route since you are designing an organizer and you will probably need the extra information any way (for example, to display the schedule of an accompanist). I would have a list of all accompanists with a sorted array of all of their bookings (basically , their schedule). You can then have a function to check for every new booking whether there is a conflict (in the booking array) for each of the accompanists in the booking and if not, continue by adding the booking to the accompanists' schedule
If I understand you correctly, you must compare values per column. I suggest to create your own RoomList collection class by deriving it from your actual collection type and to add it the possibility to iterate columns
public class RoomList : List<Event[]>
{
public IEnumerable<Event> TimeSlot(int columnIndex)
{
foreach (Event[] events in this) {
if (events != null && columnIndex < events.Length) {
yield return events[columnIndex];
}
}
}
}
Then you can use LINQ to get double bookings
RoomList roomList = new RoomList();
// ...
for (int slot = 0; slot < roomList[0].Length; slot++) {
var doubleBooked = roomList.TimeSlot(slot)
.GroupBy(e => e.Type)
.Where(g => g.Count() > 1);
if (doubleBooked.Any()) {
Console.WriteLine("Doubly booked accompanists in slot {0}", slot);
foreach (var accompanistGroup in doubleBooked) {
Console.WriteLine(" {0}", accompanistGroup.Key);
}
}
}

Categories