Show recursive row count in Devexpress XtraGrid GroupRow - c#

I have a gridview with multiple groups and I use the CustomDrawGroupRow Event to display the row count for each group:
private void gridView_CustomDrawGroupRow(object sender, DevExpress.XtraGrid.Views.Base.RowObjectCustomDrawEventArgs e)
{
var view = (GridView)sender;
var info = (GridGroupRowInfo)e.Info;
var caption = info.Column.Caption;
if (info.Column.Caption == string.Empty)
{
caption = info.Column.ToString();
}
info.GroupText = $"{caption} : {info.GroupValueText} ({view.GetChildRowCount(e.RowHandle)})";
}
Now I would like to display the row count recursively, so that the first level shows a count of 2171 (1913 + 135 + 123).
This is what I have tried, but it throws a StackOverflowException and I cannot see the problem here:
private void gridView_CustomDrawGroupRow(object sender, DevExpress.XtraGrid.Views.Base.RowObjectCustomDrawEventArgs e)
{
var view = (GridView)sender;
var info = (GridGroupRowInfo)e.Info;
var caption = info.Column.Caption;
if (info.Column.Caption == string.Empty)
{
caption = info.Column.ToString();
}
info.GroupText = $"{caption} : {info.GroupValueText} ({GetRowCountRecursive(view, e.RowHandle)})";
}
private int GetRowCountRecursive(GridView view, int rowHandle)
{
int totalCount = 0;
int childrenCount = view.GetChildRowCount(rowHandle);
for (int i = 0; i < childrenCount; i++)
{
var childRowHandle = view.GetChildRowHandle(rowHandle, i);
totalCount += GetRowCountRecursive(view, childRowHandle);
}
return totalCount;
}

I was missing to check if childRowHandle is a group row with IsGroupRow(). If not, the recursion have to stop and totalCount need to be increased by 1.
private int GetRowCountRecursive(GridView view, int rowHandle)
{
int totalCount = 0;
int childrenCount = view.GetChildRowCount(rowHandle);
for (int i = 0; i < childrenCount; i++)
{
var childRowHandle = view.GetChildRowHandle(rowHandle, i);
if (view.IsGroupRow(childRowHandle))
{
totalCount += GetRowCountRecursive(view, childRowHandle);
}
else
{
totalCount++;
}
}
return totalCount;
}

You can use GroupRowInfo.ChildControllerRowCount property to get the row count. The instance of GroupRowInfo class you can get from GridGroupRowInfo.RowKey property.
Here is example:
private void gridView1_CustomDrawGroupRow(object sender, RowObjectCustomDrawEventArgs e)
{
var view = (GridView)sender;
var info = (GridGroupRowInfo)e.Info;
var caption = info.Column.Caption;
if (info.Column.Caption == string.Empty)
{
caption = info.Column.ToString();
}
var groupInfo = info.RowKey as GroupRowInfo;
info.GroupText = $"{caption} : {info.GroupValueText} ({groupInfo?.ChildControllerRowCount})";
}
Here is the screenshot:

Related

Search by DisplayMember in ListBox

I have a ListBox which I populate like this:
var dtCustomers = db.GetTableBySQL(query).AsEnumerable().Select(rows =>
new CustomersModel
{
Name = rows.Field<string>("Name"),
ProjectKey = rows.Field<int>("ProjectKey")
});
lstCustomers.DataSource = dtCustomers.ToList();
lstCustomers.DisplayMember = "Name";
lstCustomers.ValueMember = "ProjectKey";
lstCustomers.ClearSelected();
Now I want to create TextBox with search button to look inside this list and search by item selected as:
private void btnSearch_Click(object sender, EventArgs e)
{
lstCustomers.SelectedItems.Clear();
for (int i = lstCustomers.Items.Count - 1; i >= 0; i--)
{
if (lstCustomers.Items[i].ToString().ToLower().Contains(txtSearch.Text.ToLower()))
{
lstCustomers.SetSelected(i, true);
}
}
lblitems.Text = lstCustomers.SelectedItems.Count.ToString() + "items found";
}
Problem is it never finds anything. I think it is because it is comparing by ValueMember instead of DisplayMember. Can I search in the list by DisplayMember?
You can use pattern matching for this since the underlying items will be your CustomersModel:
private void btnSearch_Click(object sender, EventArgs e)
{
lstCustomers.SelectedItems.Clear();
int matchCount = 0;
for (int i = lstCustomers.Items.Count - 1; i >= 0; i--)
{
if (lstCustomers.Items[i] is CustomersModel customer &&
customer.Name.IndexOf(txtSearch.Text, StringComparison.OrdinalIgnoreCase) > -1)
{
matchCount++;
lstCustomers.SetSelected(i, true);
}
}
lblItems.Text = $"{matchCount} item{(matchCount > 1 ? "s" : "")} found";
}

How do i update the sorted column back into the datagridview and also changing its respective items

Image of the application
So there are 4 columns ID,ItemName,ItemCategory and PriceAmount
What i want to do is sort the price amount by clicking the dropdown menu and i took out the price amount added it in an arrray and then sorted it using bubble sort but i am having a hard time finding out how do i update the datagridview as per the price amount and changing its respective columns too any help?
This is what i did
int[] price = new int[dataGridView1.Rows.Count];
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
price[i] = Convert.ToInt32(dataGridView1.Rows[i].Cells[3].Value);
for (int j = 0; j < price.Length; j++)
{
Console.WriteLine(price[j]);
}
}
int temp;
for (int j = 0; j <= price.Length - 2; j++)
{
for (int i = 0; i <= price.Length - 2; i++)
{
if (price[i] > price[i + 1])
{
temp = price[i + 1];
price[i + 1] = price[i];
price[i] = temp;
}
}
}
foreach(int sortedArray in price)
{
Console.Write(sortedArray + " ");
Console.ReadLine();
}
here is the code of the browse button
private void browseBtn_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Files(*.txt, *.csv)|*.txt;*.csv|All Files (*.*) |*.*";
DialogResult result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
pathTextBox.Text = dialog.FileName;
}
if(pathTextBox.Text.Length>0)
{
importBtn.Enabled = true;
}
}
then it grabs the location of the .csv file and then adds in the textbox
and then the insert button imports it
private void importBtn_Click(object sender, EventArgs e)
{
string value = pathTextBox.Text;
importCSVDataFile(value);
pathTextBox.Text = "";
}
Code of importCSVDataFile
private void importCSVDataFile(string filepath)
{
try
{
TextFieldParser csvreader = new TextFieldParser(filepath);
csvreader.SetDelimiters(new string[] { "," });
csvreader.ReadFields();
//int row_count = 0;
while (!csvreader.EndOfData)
{
string[] fielddata = csvreader.ReadFields();
dataGridView1.Rows.Add();
for (int i = 0; i < fielddata.Length; i++)
{
dataGridView1.Rows[row_count].Cells[i].Value = fielddata[i];
}
row_count++;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Import CSV File", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
I was performing a insert and update work with this
private void insertBtn_Click(object sender, EventArgs e)
{
string id = idTextBox.Text;
string itemName = itemNameTextBox.Text;
string itemCategory = categoryTextBox.Text;
string itemPrice = priceTextBox.Text;
if (this.status)
{
dataGridView1.Rows[this.row].Cells[0].Value = id;
dataGridView1.Rows[this.row].Cells[1].Value = itemName;
dataGridView1.Rows[this.row].Cells[2].Value = itemCategory;
dataGridView1.Rows[this.row].Cells[3].Value = itemPrice;
this.insertBtn.Text = "Save";
dataGridView1.Rows[this.row].Selected = true;
MessageBox.Show("Existing Record Updated");
}
else
{
int count = dataGridView1.Rows.Count;
dataGridView1.Rows[count].Cells[0].Value = id;
dataGridView1.Rows[count].Cells[1].Value = itemName;
dataGridView1.Rows[count].Cells[2].Value = itemCategory;
dataGridView1.Rows[count].Cells[3].Value = itemPrice;
dataGridView1.Rows[count].Selected = true;
MessageBox.Show("New Record Saved!!");
row_count++;
}
itemNameTextBox.Text = "";
categoryTextBox.Text = "";
priceTextBox.Text = "";
this.status = false;
this.row = 0;
}
Sort Algorithm of Bubble Sort for itemName
private void sortByItem()
{
int rows = dataGridView1.Rows.Count;
for (int i = 0; i < rows; i++)
{
for (int j = 1; j < rows; j++)
{
string val1 = Convert.ToString(dataGridView1.Rows[j - 1].Cells[0].Value);
string val2 = Convert.ToString(dataGridView1.Rows[j].Cells[0].Value);
if (string.Compare(val1, val2) > 0)
{
for (int a = 0; a < this.dataGridView1.Columns.Count; a++)
{
object temp = this.dataGridView1[a, j - 1].Value;
this.dataGridView1[a, j - 1].Value = this.dataGridView1[a, j].Value;
this.dataGridView1[a, j].Value = temp;
}
}
}
}
The way you are binding data in the gridview is not right. That way you can not achieve functionality of sorting GridView.
What you need is a class which represents the data which are loading in the gridview.
Let say you have a product class as following.
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
}
Now you have data of multiple products in the file so you need to create a list of products and initialize it in the form as following.
public partial class Form1 : Form
{
private List<Product> products;
public Form1()
{
products = new List<Product>();
InitializeComponent();
}
}
Now method importCSVDataFile needs to be changed as following to load data from the file and populate the list and bind it to the grid view.
private void importCSVDataFile(string filepath)
{
try
{
TextFieldParser csvreader = new TextFieldParser(filepath);
csvreader.SetDelimiters(new string[] { "," });
csvreader.ReadFields();
while (!csvreader.EndOfData)
{
string[] fielddata = csvreader.ReadFields();
var product = new Product();
product.ID = Convert.ToInt32(fielddata[0]);
product.Name = fielddata[1];
product.Category = fielddata[2];
product.Price = Convert.ToDouble(fielddata[3]);
products.Add(product);
}
dataGridView1.DataSource = products;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Import CSV File", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Once you have data displayed in the grid, you need to change sorting code as following.
Let say you have combobox1 which has column names listed to sort the gridview. So code of combobox1's SelectedIndexChanged event will be as following.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedItem != null)
{
var selectedColumnName = comboBox1.SelectedItem as string;
switch (selectedColumnName)
{
case "ID":
dataGridView1.DataSource = products.OrderBy(product => product.ID).ToList();
break;
case "Name":
dataGridView1.DataSource = products.OrderBy(product => product.Name).ToList();
break;
case "Category":
dataGridView1.DataSource = products.OrderBy(product => product.Category).ToList();
break;
case "Price":
dataGridView1.DataSource = products.OrderBy(product => product.Price).ToList();
break;
}
}
}
To add new products to the gridview you need to create new instance of Product and set its properties and then add it to the list and bind the list to the gridview again.
public void btnInsert_Click(object sender, EventArgs e)
{
var id = Convert.ToInt32(txtId.Text);
var objProduct = products.FirstOrDefault(product => product.ID == id);
if (objProduct == null)
{
objProduct = new Product();
objProduct.ID = id;
objProduct.Name = txtName.Text;
objProduct.Category = txtCategory.Text;
objProduct.Price = Convert.ToDouble(txtPrice.Text);
products.Add(objProduct);
}
else
{
objProduct.Name = txtName.Text;
objProduct.Category = txtCategory.Text;
objProduct.Price = Convert.ToDouble(txtPrice.Text);
}
dataGridView1.DataSource = null;
dataGridView1.DataSource = products;
}
I hope this would help you resolve your issue.

Populate TextBoxes from a List

I am trying to populate TextBoxes from a list. I have been able to populate ComboBoxes with comboList:
var comboList = new System.Windows.Forms.ComboBox[4];
comboList[0] = cmbSite1Asset;
comboList[1] = cmbSite2Asset;
comboList[2] = cmbSite3Asset;
comboList[3] = cmbSite4Asset;
List<CRCS.CAsset> assets = _rcs.Assets;
foreach (CRCS.CAsset asset in assets)
{
string id = asset.ID;
for (int i = 0; i < 4; ++i)
{
comboList[i].Items.Add(id);
}
}
But when I try and apply the same principle to TextBoxes
var aosList = new System.Windows.Forms.TextBox[8];
aosList[0] = txtAsset1;
aosList[1] = txtAsset2;
aosList[2] = txtAsset3;
aosList[3] = txtAsset4;
aosList[4] = txtAsset5;
aosList[5] = txtAsset6;
aosList[6] = txtAsset7;
aosList[7] = txtAsset8;
foreach (CRCS.CAsset asset in assets)
{
string id = asset.ID;
for (int n = 0; n < 8; ++n)
{
aosList[n].Items.Add(id);
}
}
TextBox does not like Items.Add ( aosList[n]Items.Add(id); )
I am looking fore a reference or guidance resolving this issue. Thanks!
You should use ComboBox for your problem,instead of iterating on each element,You simply use below lines to populate combobox.
comboList.DataSource=assets;
comboList.DisplayMember="ID";
comboList.ValueMember="ID";
However,if you want your values in TextBox,you can use TextBox.AppendText Method, but it will not work like ComboBox as it will contain texts+texts+texts, will not have indexes like ComboBox.
private void AppendTextBoxLine(string myStr)
{
if (textBox1.Text.Length > 0)
{
textBox1.AppendText(Environment.NewLine);
}
textBox1.AppendText(myStr);
}
private void TestMethod()
{
for (int i = 0; i < 2; i++)
{
AppendTextBoxLine("Some text");
}
}
A Combobox is a collection of items, and so has an Items property from which you can add/remove to change it's contents. A Textbox is just a control that displays some text value, so it has a Text property which you can set/get, and which denotes the string that is displayed.
System.Windows.Forms.TextBox[] aosList = new System.Windows.Forms.TextBox[8];
aosList[0] = txtAsset1;
aosList[1] = txtAsset2;
aosList[2] = txtAsset3;
aosList[3] = txtAsset4;
aosList[4] = txtAsset5;
aosList[5] = txtAsset6;
aosList[6] = txtAsset7;
aosList[7] = txtAsset8;
for (int n = 0; n < 8; ++n)
{
aosList[n].Text = assets[n].ID; // make sure you have 8 assets also!
}
int i = 1;
foreach (var asset in assets)
{
this.Controls["txtAsset" + i].Text = asset.ID;
i++;
}

Save and Restore Row Selections in a DataGrid

The problem is fairly straightforward. My datagrid is filled from my ItemSource (bindingList) which is basically a list of objects filled with strings.
In this certain part of my code I need to update my bindingList.
Unfortunately when it is updated all user row selections made in the DataGrid dissappear.
This is an inconvenience to the user that I would like to remedy. So that when the user clicks the button that results in the bindingList to update, the selections are saved in case the user wants to make further changes.
Code Right Now:
//Save DataGrid Row Selections
bindingList[1] = (new ItemClass() { columnnumber = colonum, xcoord = xpos, ycoord = ypos, description = descrip });
dataGrid.ItemSource = bindingList;
//Restore DataGrid Row Selections
EDIT:
Wider scope as requested:
private void Y_Incre_Button_Click(object sender, RoutedEventArgs e)
{
if (dataGrid.SelectedItems.Count != 0)
{
string colonum;
string xpos;
string ypos;
string descrip;
for (int i = 0; i < bindingList.Count; i++)
{
int selectionIndex = dataGrid.SelectedIndex;
if (selectionIndex > -1)
{
var curItem = bindingList[selectionIndex];
int yNum = int.Parse(curItem.ycoord);
int yNum2 = (yNum + 10);
colonum = curItem.columnnumber;
xpos = curItem.xcoord;
ypos = yNum2.ToString();
descrip = curItem.description;
//Save DataGrid Row Selections
bindingList[selectionIndex] = (new ItemClass() { columnnumber = colonum, xcoord = xpos, ycoord = ypos, description = descrip });
//Restore DataGrid Row Selections
}
}
}
else
{
MessageBox.Show("No Rows Selected");
}
}
To get this working store the row indices of the selected items before they are replaced, and then reselect those records after the "replace" operation is complete. Also, have a look at this example.
private void Y_Incre_Button_Click(object sender, RoutedEventArgs e)
{
if (dataGrid.SelectedItems.Count != 0)
{
// Save DataGrid Row Selections
List<int> selectedRowIndexList = new List<int>();
foreach (object item in dataGrid.SelectedItems)
{
selectedRowIndexList.Add(dataGrid.Items.IndexOf(item));
}
for (int i = 0; i < bindingList.Count; i++)
{
int selectionIndex = dataGrid.SelectedIndex;
if (selectionIndex > -1)
{
ItemClass curItem = bindingList[selectionIndex];
int yNum = int.Parse(curItem.ycoord);
int yNum2 = yNum + 10;
string colonum = curItem.columnnumber;
string xpos = curItem.xcoord;
string ypos = yNum2.ToString();
string descrip = curItem.description;
bindingList[selectionIndex] = new ItemClass { columnnumber = colonum, xcoord = xpos, ycoord = ypos, description = descrip };
}
}
// Restore DataGrid Row Selections
dataGrid.SelectedItems.Clear();
foreach (int rowIndex in selectedRowIndexList)
{
if (rowIndex < 0 || rowIndex > dataGrid.Items.Count - 1)
throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));
object item = dataGrid.Items[rowIndex];
dataGrid.SelectedItems.Add(item);
}
}
else
{
MessageBox.Show("No Rows Selected");
}
}

Summing GridView column values

I have a "time duration" column in a grid view, and I wish to sum that particular column in C# and publish the total time taken at a label named Total Time. How can I do this?
Sample code:
int sum = 0;
for (int i = 0; i < dgTestSteps.SelectedColumns.Count; ++i)
{
sum += Convert.ToInt32(dgTestSteps.SelectedColumns.Count.ToString());
//sum += Convert.ToInt32(dgTestSteps.Rows[i].Cells[1].Value);
}
lblTotalTime.Text = sum.ToString();
int sum = 0;
for (int i = 0; i < dgTestSteps.Rows.Count; ++i)
{
sum += Int.Parse(dgTestSteps.Rows[i].Cells[1].Value.ToString());
}
lblTotalTime.Text = sum.To String();
It seems true! Is there any problem with this?
I do this by aggregating the column row values in a class variable thusly:
Code behind:
protected void ItemsGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// Get the row variable values
var rowView = (DataRowView)e.Row.DataItem;
var itemId = Convert.ToInt32(rowView["ItemID"]);
var skuId = Convert.ToInt32(rowView["ItemSkuID"]);
var quantity = Convert.ToInt32(rowView["Quantity"]);
// Instantiate instances of the relevant classes
var item = new Item(itemId);
var sku = new Sku(skuId);
var itemPrice = String.Format("{0:n2}", (item.Price + sku.PriceAdj));
var itemPriceLiteral = (Literal)e.Row.FindControl("ItemPrice");
if (itemPriceLiteral != null)
{
itemPriceLiteral.Text = itemPrice;
}
var itemExtendedPriceLiteral = (Literal)e.Row.FindControl("ItemExtendedPrice");
if (itemExtendedPriceLiteral != null)
{
var extendedPrice = price * quantity;
itemExtendedPriceLiteral.Text = String.Format("{0:n2}", extendedPrice);
// Increment the extended price
_totalExtendedPrice += extendedPrice;
}
}
}
// Lots of stuff omitted from this method for clarity
public void GetSummary()
{
// Set the text property of the total literal below the GridView
OrderTotal.Text = string.Format((HttpUtility.HtmlDecode(
(string)GetGlobalResourceObject("ShopStrings", "UsdPrice"))),
_totalExtendedPrice);
}
OrderTotal.Text is localized but you can easily format it without using resources.
You can use this but you should know that number of columns start with 0 and goes up with 1
int sum = 0;
for (int i = 0; i < dgTestSteps.Rows.Count; ++i)
{
sum += Convert.ToInt32(dgTestSteps.Rows[i].Cells[0].Value);
}
lblTotalTime.Text = sum.ToString();
I tried this and has no problem

Categories

Resources