How to populate over multiple columns? - c#

I am working on an 'attendance' system for the school I work at. I'm using a listview for this, but noticed that there's simply not enough space to house all employees in one list. So I want to add a second column (or at least, another 'instance' of the three columns currently used). How do I manage to get such job done?
I have been thinking about workarounds such as a second listview instance, but that would make things only more complex in my opinion. So I'd like to stick by one.
See image below for visual description of what I want to get; I want the output to first fill the left part of the listview, then the right one.
And this is the part of my code that it's (probably) about:
public void LoadEmployees()
{
lvEmployees.View = View.Details;
lvEmployees.GridLines = true;
lvEmployees.Items.Clear();
List<Employees> data = database.LoadEmployees();
for (int i = 0; i < data.Count; i++)
{
// Define the list items
ListViewItem emp = new ListViewItem(data[i].Abbreviation);
emp.SubItems.Add(data[i].Name);
emp.SubItems.Add(data[i].Status);
// Add the list items to the ListView
lvEmployees.Items.Add(emp);
}
}

With lvEmployees1 and lvEmployees2 (or left/right), two ListView.
We can simply split the List<Employees> data in half.
lvEmployees1.View = View.Details;
lvEmployees1.GridLines = true;
lvEmployees1.Items.Clear();
lvEmployees2.View = View.Details;
lvEmployees2.GridLines = true;
lvEmployees2.Items.Clear();
List<Employees> data = database.LoadEmployees();
int half = data.Count / 2;
for (int i = 0; i < data.Count; i++)
{
// Define the list items
ListViewItem emp = new ListViewItem(data[i].Abbreviation);
emp.SubItems.Add(data[i].Name);
emp.SubItems.Add(data[i].Status);
if (i <= half) lvEmployees1.Items.Add(emp);
else lvEmployees2.Items.Add(emp);
}
To create the second "grid", you can simply copy past the element in the designer.

Related

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.

How to update text in a listview without clearing it

I am making a hack for a game that reads all of the enemies information (location, health, etc.) and stores it accordingly in a listview. I am using this method currently:
listView1.Items.Clear();
string[] arr = new string[6];
ListViewItem item;
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
arr[0] = i.ToString()
arr[1] = engine.enemy[i].name.ToString();
arr[2] = engine.enemy[i].getRank(csgo.enemy[i].compRank);
arr[3] = engine.enemy[i].Wins.ToString();
arr[4] = engine.enemy[i].health.ToString();
arr[5] = engine.enemy[i].armor.ToString();
item = new ListViewItem(arr);
listView2.Items.Add(item);
}
This is being done every 200 ms to assure real time information.
This works to an extent. It does show everything correctly. However, it has 2 big flaws.
1: it flickers constantly when it clears and rewrites the data.
2: If my listview is only 10 columns long and i need to read data for 20 players, i can't scroll down to see the last 10 players because each time it clears, it resets the scroll bar position.
So is it possible to ONLY update the text of a specific text? Say i only want to update enemy[3]'s health and leave the rest. Can this be done? I don't need to redraw some information like wins and rank because they won't be changing during the game.
Yes, you can use the indexer to directly reference the item you wish to update. For example:
listView2.Items[playerIndex] = new ListViewItem(arr);
As for accomplishing this relative to your game engine, you'll want to use the Reconciliation Design Pattern, which involves the following given game engine list (Master), and list view (Target):
Create a temporary of items or keys in Target. Call this ToDelete.
Step through Master, add any entries that aren't in Target, update those that are. Delete from ToDelete for each match to Target.
Delete from Target all items remaining in ToDelete.
Update: as Slai mentions, you can update via the SubItems member if you don't want to even replace an entire row.
I believe you have to first initially fill the list view with the data from the first call, and then every 500ms-1000ms (I think that 200ms offers nothing), refresh the list view with fresh data.
Something like:
private void InitialPopulation()
{
var listOfItems = new List<ListViewItem>();
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
var item = new ListViewItem(i.ToString());
item.Name = engine.enemy[i].name;
item.SubItems.Add(engine.enemy[i].name);
item.SubItems.Add(engine.enemy[i].getRank);
item.SubItems.Add(engine.enemy[i].Wins);
item.SubItems.Add(engine.enemy[i].health);
item.SubItems.Add(engine.enemy[i].armor);
listOfItems.Add(item);
}
listView1.BeginUpdate();
listView1.Items.Clear();
listView1.Items.AddRange(listOfItems.ToArray());
listView1.EndUpdate();
}
private void RefreshData()
{
listView1.BeginUpdate();
var listOfItems = new List<ListViewItem>();
var playersNames = new List<string>();
var itemsNames = new List<string>();
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
playersNames.Add(engine.enemy[i].name);
var items = listView1.Items.Find(engine.enemy[i].name, false);
switch (items.Length)
{
case 1: // update
items[0].SubItems[0].Text = engine.enemy[i].name;
items[0].SubItems[1].Text = engine.enemy[i].getRank;
items[0].SubItems[2].Text = engine.enemy[i].Wins;
items[0].SubItems[3].Text = engine.enemy[i].health;
items[0].SubItems[4].Text = engine.enemy[i].armor;
break;
case 0: // add
var item = new ListViewItem(i.ToString());
item.Name = engine.enemy[i].name;
item.SubItems.Add(engine.enemy[i].name);
item.SubItems.Add(engine.enemy[i].getRank);
item.SubItems.Add(engine.enemy[i].Wins);
item.SubItems.Add(engine.enemy[i].health);
item.SubItems.Add(engine.enemy[i].armor);
listOfItems.Add(item);
break;
}
}
if (listOfItems.Count > 0)
listView1.Items.AddRange(listOfItems.ToArray());
foreach (ListViewItem item in listView1.Items)
itemsNames.Add(item.Name);
// check if there are more listview items than data.
if (itemsNames.Count > playersNames.Count)
foreach (var name in itemsNames.Except(playersNames))
listView1.Items.RemoveByKey(name);
listView1.EndUpdate();
}
I assumed that engine.enemy[i].name is unique for each palyer.
Maybe there are some errors in the code since I have not written anything for winforms for a long time, but I think you should get the meaning.

JSON data List to combo box

I know this has been asked quite a few times and I know ot will be really simple but I'm really new to C# and I'm pulling my hair out because I've been coding (not very well) through the night. I have a class ProcessOrdersActive that I Deserialiaze to details. It's falling over when I try to add ProcessOrderNbr[I] to the combobox.
//Deserialise data
ProcessOrdersActive details = JsonConvert.DeserializeObject<ProcessOrdersActive>(responseBody);
var ordersList = new List<ProcessOrdersActive>();
ordersList.Add(details);
int numofitems = ordersList.Capacity;
txtActiveOrders.Text = numofitems.ToString();
for (int i = 0; i < numofitems; i++)
{
comboBoxOrders.Items.Add (details.ProcessOrderNbr[i]);
}
Many thanks for the responses. I had a good chat with a proficient C# programmer today and here is the solution that he came to. The names are slightly different from the original post.
//Deserialise data & send to DataGrid
ProcessOrderDetails details = JsonConvert.DeserializeObject<ProcessOrderDetails>(responseBody);
//Get number of operations and display to screen
int numofitems = details.MaterialList.Count<Materiallist>();
txtNumOfMaterials.Text = numofitems.ToString();
//Find the last operation
var lastone = details.MaterialList.Last<Materiallist>();
//Create a new material/operation list
var materialList = new List<Materiallist>();
//Add the last operation to the list
materialList.Add(lastone);
//Parse the list to the data grid
dataGridProcessOrderDetails.DataSource = materialList;
You are trying to access an index that may be out of bounds of the array/list.
The number of items should be the Length/Count of the array/List you are accessing.
//Deserialise data
ProcessOrdersActive details = JsonConvert.DeserializeObject<ProcessOrdersActive>(responseBody);
var ordersList = new List<ProcessOrdersActive>();
ordersList.Add(details);
int numofitems = details.ProcessOrderNbr.Count;//If this is a list use Count. If it is an array use Length
txtActiveOrders.Text = numofitems.ToString();
for (int i = 0; i < numofitems; i++) {
comboBoxOrders.Items.Add (details.ProcessOrderNbr[i]);
}

How do I add for() loop index identifier to label name?

I am attempting to dynamically add content to labels in WPF from an object in a list.
I can add the content from the Object fine by hard-coding the index number:
// assign forecast values to 5 user interface labels
Forecast fc1 = day.Weather.getForecastInList(0);
day1Label.Content = fc1.Day;
day1ConditionLabel.Content = fc1.ForecastCondition;
day1TempLabel.Content = formatHiLow(fc1.TemperatureHigh, fc1.TemperatureLow);
Forecast fc2 = day.Weather.getForecastInList(1);
day2Label.Content = fc2.Day;
day2ConditionLabel.Content = fc2.ForecastCondition;
day2TempLabel.Content = formatHiLow(fc2.TemperatureHigh, fc2.TemperatureLow);
etc...
But then I would have to repeat a lot of the assignment code for each of the 5 labels.
I have attempted to use a for() loop which works fine to get each of the objects from the list but won't let me rename the labels e.g.
// assign forecast values to 5 user interface labels
for (int i = 0; i < 5; i++ )
{
Forecast fc+(i+1) = day.Weather.getForecastInList(i);
day+(i+1)+Label.Content = fc+(i+1)+.Day;
}
How can I dynamically identify WPF labels using the index in a for() loop?
I don't recommend this but to answer your question, theres a possibility:
Create a list which contains all your labels:
List<Label> dayLabels = new List<Label>();
dayLabels.Add(day1Label);
dayLabels.Add(day2Label);
//and so on...
Loop through all the labels:
for (int i = 0; i < 5; i++ )
{
Forecast fc = day.Weather.getForecastInList(i);
dayLabels.ElementAt(i).Content = fc.Day;
}

How do you add EmptySpaceItems and LayoutControlItems with equal widths to a LayoutControlGroup with DevExpress?

NOTE: All controls described in this question are DEVEXPRESS controls.
I am attempting to programatically add both LayoutControlItems and EmptySpaceItems to a LayoutControlGroup on a DevExpress LayoutControl.
The requirements for my project require that I have a LayoutControlGroup panel that is dependent on a set of filter items chosen from another control on the layout control. If there are no filters chosen, than none of the `LayoutControlItems are shown. If one or more of the filters are chosen that I add one or more of the controls to the group based on the selection.
The way that I attempt to do this is the following:
1) in the designer for the LayoutControl I have already created the LayoutControlGroup with the LayoutControlItems. There are 6 total, and each LayoutControlItem contains a PanelControl that contains a ListBoxControl
2) When the form is initalized I hide each of the LayoutControlItems from the LayoutControlGroup using the LayoutControlItem.HideFromCustomization() method.
3) After the user selects a filter or set of filters I run the following code attempting to restore the control items to the group from left to right.
this.layoutGroupScenarioPortfolios.BeginUpdate();
LayoutControlItem layoutControlToAdd;
LayoutControlItem lastLayoutControlItem = null;
for (int loop = 0; loop < selectedFilters.Length; loop++)
{
layoutControlToAdd = LayoutControlItemFactoryUtil(selectedFilters[loop]);
if (layoutControlToAdd == null)
{
continue;
}
if (loop < 1)
{
layoutControlToAdd.RestoreFromCustomization(this.layoutControlGroupSelectedFilters);
}
else
{
layoutControlToAdd.RestoreFromCustomization(lastLayoutControlItem, DevExpress.XtraLayout.Utils.InsertType.Right);
}
lastLayoutControlItem = layoutControlToAdd;
}
for (int loop = 0; loop < numOfEmptySpaceItemsNeeded; loop++)
{
layoutControlToAdd = new EmptySpaceItem(this.layoutControlGroupSelectedFilters)
{
Owner = this.layoutControlGroupSelectedFilters.Owner
};
layoutControlToAdd.RestoreFromCustomization(lastLayoutControlItem, DevExpress.XtraLayout.Utils.InsertType.Right);
lastLayoutControlItem = layoutControlToAdd;
}
this.layoutControlGroupSelectedFilters.TextVisible = true;
this.layoutGroupScenarioPortfolios.EndUpdate();
As you can see from the code one loop adds the appropriate ListControlBox to the group. The second loop tries to add empty space items to make sure that the list box control does not take up the entire group. By the end of this code there should be 6 items spanning the group control, each with the same width in the control.
The problem is that the first control added takes up half of the group box's space while the other 5 items are equally fitted into the remaining half of the group box.
In the first loop, is the RestoreFromCustomization() method with one parameter the correct method to use?
I would suggest to place the controls at runtime. LayoutControl will manage the LayoutControlGroups and EmptySpaceItems.
Here is the code I had written to place user controls into the LayoutControl at runtime:
LayoutControlItem lastItem = null;
int RowWidth = 0;
public void AddMyControl()
{
MyControl myControl = new MyControl("");
myControl.Name = Guid.NewGuid().ToString();
LayoutControlItem item = new LayoutControlItem();
item.Name = Guid.NewGuid().ToString();
item.Text = "";
MyLayoutControl.BeginUpdate();
//We need to determine where to insert the new item. Right or Below. If there is
//space on the right we insert at Right else we just add the item.
if(lastItem == null || lastItem != null && (MyLayoutControl.Width - UserControlWidth) < RowWidth)
{
MyLayoutControl.AddItem(item);
RowWidth = item.MinSize.Width;
}
else
{
MyLayoutControl.AddItem(item, lastItem, DevExpress.XtraLayout.Utils.InsertType.Right);
}
item.Control = myControl;
RowWidth += item.MinSize.Width;
lastItem = item;
item.Name = " ";
MyLayoutControl.BestFit();
MyLayoutControl.EndUpdate();
}
If you just need left to right controls, flowlayoutpanel will suit better. Sometimes LayoutControl is tough to work with. I had eventually go with flowlayoutpanel as it is much simpler to work with.

Categories