I would like to create a cell for the datagridview that would accept a Int64 value in order to sort.
Additionally that cell will display an extra value that is the difference of the current value with a reference value I have outside the datagridview.
I could do it as string but the sorting will not be correctly handled because it would look like 1, 10, 11, 2.... and so on.
So I thought that if I could create a custom cell and define the cell value the long and display a string it would be great... but I'm not sure if this can be accomplished....
Does someone know how can this be accomplished in a simple way? Note that I am loading the datagridview manually but I am defining the column types to allow sorting.
One fairly easy way to do this is to use the DataGridView's SortCompare event. Use the event to check that the column being sorted displays your custom data and if so extract the number portion of that data and do your sort on it.
Below I have an example:
private void dataGridView1_SortCompare(object sender, DataGridViewSortCompareEventArgs e) {
if (e.Column.Index == 1) { // This is your custom data's column.
// Extract the numeric values from the cells being compared for the sort.
// BEWARE: code assumes you'll always be able to extract a long from the cell contents.
long cell1NumericValue = Int64.Parse(e.CellValue1.ToString().Split(' ')[0]);
long cell2NumericValue = Int64.Parse(e.CellValue2.ToString().Split(' ')[0]);
// Compare these numeric values to determine how to sort.
e.SortResult = cell1NumericValue.CompareTo(cell2NumericValue);
e.Handled = true;
}
}
Assumptions:
- that the column with your custom data is at column index 1
- that your custom data consists of a number followed by at least one space
My code also assumes that the conversion of the cells' value will never throw an error. It's possible that your data includes values that would cause this conversion to fail. What you could do in that case is validate your data before the conversion (that it's not null, etc.) and if of the validation fails set the cell's numeric value for sorting purposes to -1 or something so it's always lower than valid values in other cells. (I hope that made sense).
Applying these types of sorts is pretty well described in this MSDN article. You'll probably want to take a look. One of the examples show what you can do in the case of ties (the example shows sorting on another column as the sort tiebreaker).
Related
I don't know if this is even possible, but I have a large datagrid in my WinForms C# app and I want the user to be able to select any cell and mark it as 'Not Applicable' or some other such note (String). This is fine when the column contains Strings already, but I can't find away to post a String note into a cell which is in a Column designated for Dates. Short of converting the whole column of dates into string values, does anyone know of a way to achieve this kind of effect?
If your column type is DateTime it accepts DateTime values and null only (period).
With a Framework like WPF cou could have a fancy composite control for a single cell which does the tricks you want.
With WinForms you should use the column format string which allows for all kinds of values and do all the processing yourself.
I have a series of lists and classes that implement a table of data. The basic classes are: Columns, Rows, and Cells. The Rows contains some ID information and list of Cells which contains the row's value for each column. Currently I create the rows in a cell with code like this
void CreateRow()
{
Row newRow = new Row();
newRow.ID = idInfo;
foreach (var Column in Columns)
{
newRow.Cells.Add(new Cell(Column.ID));
}
Rows.Add(newRow);
}
The works fine, but in some cases am calling CreateRow() 20,000 times and have 200+ columns. So I am wondering if there is a more efficient way to populate the cells since the cells in a certain column in each row are identical.
Any ideas?
Thanks,
Jerry
Currently you create unique Cell object for each position in your matrix - that's a lot of cells given your use case of 20.000 + rows.
One approach to be more efficient could be to not add the cells at all when you construct the matrix, but only when you try to get or set its value (i.e using Lazy<T>).
Assuming you set the value of a cell before retrieving it, you could then have a factory method for creating a cell with a value - make the Cell object immutable and when you are "creating" a Cell for which you already have another cell with an identical value, return that cell instead. This could reduce the total number of Cell objects significantly, of course there's more overhead since you need to check whether you have a cell of the same value already and need to call the factory method again if you need to update the value of a cell.
Then again all of this could not be worth it if you do not experience any memory/performance problems with your current approach - measuring performance is key here.
Isn't Columns a collection?
var Ids = Columns.Select(c => c.Id).ToArray();
var Names = Columns.Select(c => c.Name).ToArray();
etc. Except why do that if Columns is already a collection? For you could do Columns[index].Id
Or if you must have the code you outlined:
Row newRow = new Row();
newRow.ID = idInfo;
// presuming Cells is a List<>
newRow.Cells.AddRange(Columns.Select(c => new Cell(c.Id)));
Rows.Add(newRow);
Some suggestions (depends on what you are looking for)
Consider using (strongly typed) DataSet/DataTable
If using List and you know the size, set the capacity to avoid reallocation (new List(2000))
Use struct instead of class if it makes sense
Cache objects if it makes sense (instead of duplicating the same object over and over)
You're creating the cells anyways. So I gather that the question refers to when you will fill the cells with their values, which are always in each column for all rows.
I actually think that from a correctness point of view, it makes sense to have the data duplicated, since they are in effect separate instances of the same data.
That said, if it is not really data, but you just want to show a view-column with the same value for each row, and you just want it as a data column in order to ease showing it as a view-column, then in your property-get Row.Cells(Id) you can check the ID, and if it's one of those columns where the value is always the same, return that value, bypassing looking up your _Cells collection.
If the data is mostly the same and sometimes different, you may want to use 'default values' where if the Cell object does not exist, a default value for that column will be returned. This necessitates a GetValue() method on the row, though, if you want to avoid having the Cell object altogether for places where it is default.
If you don't care about #1, you can really make a single instance of whatever the value is, and reference it in your Cell's value. This is harder to do for a Value Type than for a Reference Type (definition here) but it can be done.
Lastly, is there any reason you're not using .NET's supplied DataTable and DataRow types? I'm sure the MS geeks programmed as much efficiency as they could into those.
In my DataGridView I'am displaying a buch of columns from one table. In this table I have a column which points to item in another table. As you may already guessed, I want to display in the grid in one column some text value from the second table instead of and ItemID.
I could not find a right example on the net how to do this.
Lets assume that I have two tables in databes:
Table Users:
UserID UserName UserWorkplaceID
1 Martin 1
2 John 1
3 Susannah 2
4 Jack 3
Table Workplaces:
WorkplaceID WorkplaceName
1 "Factory"
2 "Grocery"
3 "Airport"
I have one untyped dataset dsUsers, one binding source bsUsers, and two DataAdapters for filling dataset (daUsers, daWorkplaces).
Code which I am performing:
daUsers.Fill(dsUsers);
daWorkplaces.Fill(dsUsers);
bsUsers.DataSource = dsUsers.Tables[0];
dgvUsers.DataSource = bsUsers;
At this point I see in my dgvUsers three columns, UserID, UserName and UserWorkplaceID. However, instead of UserWorkplaceID and values 1,2,3 I would like to see "Factory", "Grocery" and so on...
So I've added another column to dgvUsers called "WorkplaceName" and in my code I am trying to bind it to the newly created relation:
dsUsers.Relations.Add("UsersWorkplaces", dsUsers.Tables[1].Columns["WorkplaceID"], dsUsers.Tables[0].Columns["UserWorkplaceID"]);
WorkplaceName.DataPropertyName = "UsersWorkplaces.WorkplaceName";
Unfortunately that doesn't work. Relation is created without errors but fields in this column are empty after running the program.
What I am doing wrong?
I would like to also ask about an example with LookUp combobox in DataGridView which allow me to change the UserWorkplaceID but instead of numeric value it will show a tex value which is under WorkplaceName.
Thanks for your time.
In my opinion, the best decision would be to use the DataGridViewComboBoxColumn column type. If you do it, you should create a data adapter with lookup data beforehand and then set DataSource, DataPropertyName, DisplayMember, and ValueMember properties of the DataGridViewComboBoxColumn. You could also set the DisplayStyle property to Nothing to make the column look like a common data column. That's it.
I don't know if you can do exactly what you want, which seems to be binding the DataGridView to two different DataTable instances simulataneously. I don't think the DataGridView class supports that -- or if it does it's a ninja-style move I haven't seen.
Per MSDN, your best bet is probably using the CellFormatting event on the DataGridView and check for when the cell being formatted is in the lookup column, then you could substitute your value from the other table. Use an unbound column for the WorkplaceName column, hide the UserWorkplaceID column and then implement the CellFormatting event handle to look up the value in the row, e.g.:
private void dgv_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
if (dgv.Columns[e.ColumnIndex].Name.Equals("WorkplaceName")
{
// Use helper method to get the string from lookup table
e.Value = GetWorkplaceNameLookupValue(
dataGridViewScanDetails.Rows[e.RowIndex].Cells["UserWorkplaceID"].Value);
}
}
If you've got a lot of rows visible, this might impact performance but is probably a decent way to get it working.
If this doesn't appeal to you, maybe use the DataTable.Merge() method to merge your lookup table into your main table. A quick glance at one of my ADO.NET books suggests this should work, although I have not tried it. But I'm not sure if this is too close to the idea suggested previously which you shot down.
As for your second question about the lookup combobox, you should really post it in a separate question so it gets proper attention.
You could make SQL do the job instead. Use a join to return a table with Workplace names instead of IDs, output that table into a dataset and use it instead.
eg.
SELECT A.UserID, A.UserName, B.WorkplaceID
FROM Users A
JOIN Workplaces B ON A.UserWorkplaceID = B.WorkplaceID
Then use its output to fill dsUsers.
I am inserting a column in a DataGridView programmatically (i.e., not bound to any data tables/databases) as follows:
int lastIndex = m_DGV.Columns.Count - 1; // Count = 4 in this case
DataGridViewTextBoxColumn col = (DataGridViewTextBoxColumn)m_DGV.Columns[lastIndex];
m_DGV.Columns.RemoveAt(lastIndex);
m_DGV.Columns.Insert(insertIndex, col); // insertIndex = 2
I have found that my columns are visually out of order sometimes using this method. A workaround is to manually set the DisplayIndex property of the column afterwards. Adding this code "fixes it", but I don't understand why it behaves this way.
Console.Write(m_DGV.Columns[0].DisplayIndex); // Has value of 0
Console.Write(m_DGV.Columns[1].DisplayIndex); // Has value of 1
Console.Write(m_DGV.Columns[2].DisplayIndex); // Has value of 3
Console.Write(m_DGV.Columns[3].DisplayIndex); // Has value of 2
col.DisplayIndex = insertIndex;
Console.Write(m_DGV.Columns[0].DisplayIndex); // Has value of 0
Console.Write(m_DGV.Columns[1].DisplayIndex); // Has value of 1
Console.Write(m_DGV.Columns[2].DisplayIndex); // Has value of 2
Console.Write(m_DGV.Columns[3].DisplayIndex); // Has value of 3
As an aside, my grid can grow its column count dynamically. I wanted to grow it in chunks, so each insert didn't require a column allocation (and associated initialization). Each "new" column would then be added by grabbing an unused column from the end, inserting it into the desired position, and making it visible.
I suspect this is because the order of the columns in the DataGridView do not necessarily dictate the display order, though without explicitly being assigned by default the order of the columns dictate the DisplayIndex property values. That is why there is a DisplayIndex property, so you may add columns to the collection without performing Inserts - you just need to specify the DisplayIndex value and a cascade update occurs for everything with an equal or greater DisplayIndex. It appears from your example the inserted column is also receiving the first skipped DisplayIndex value.
From a question/answer I found:
Changing the DisplayIndex will cause
all the columns between the old
DisplayIndex and the new DisplayIndex
to be shifted.
As with nearly all collections (other than LinkedLists) its always better to add to a collection than insert into a collection. The behavior you are seeing is a reflection of that rule.
I have a couple of ideas.
How about addressing your columns by a unique name, rather than the index in the collection? They might not already have a name, but you could keep track of who's who if you gave them a name that meant something.
You can use the GetFirstColumn, GetNextColumn, GetPreviousColumn, GetLastColumn methods of the DataGridViewColumnCollection class, which work on display order, not the order in the collection. You can also just iterate through the collection using a for loop and m_DGV.Columns[i] until you find the one you want.
Create an inherited DataGridView and DataGridViewColumnCollection. The DataGridView simply is overridden to use your new collection class. Your new DataGridViewColumnCollection will include a method to address the collection by display index, presumably by iterating through the collection until you find the one you want (see #2). Or you can save a dictionary and keep it updated for very large numbers of columns.
I doubt the performance increase of keeping a dictionary, since every time a column moves, you essentially have to rewrite the entire thing. Iterating through is O(n) anyway, and unless you're talking asynchronous operations with hundreds of columns, you're probably okay.
You might be able to override the this[] operator as well, assuming it doesn't screw up the DataGridView.
Idea #1 might be the easiest to implement, but not necessarily the prettiest. Idea #2 works, and you can put it in a function DataGridViewColumn GetColumnByDisplayIndex(int Index). Idea #3 is cute, and certainly the most encapsulated approach, but isn't exactly trivial.
Thanks to cfeduke for excellent advice. I suspected Insert would be slower, but the provided link enlightened me on JUST HOW MUCH slower.
This brings up the question of how to efficiently insert and remove columns dynamically on a DataGridView. It looks like the ideal design would be to add plenty of columns using Add or AddRange, and then never really remove them. You could then simulate removal by setting the Visible property to false. And you could insert a column by grabbing an invisible column, setting its DisplayIndex and making it visible.
However, I suspect there would be landmines to avoid with this approach. Foremost being that you can no longer index your data in a straightforward manner. That is, m_DGV.Columns[i] and m_DGV.Rows[n].Cells[i] will not be mapped properly. I suppose you could create a Map/Dictionary to maintain an external intuitive mapping.
Since my application (as currently designed) requires frequent column insertion and removal it might be worth it. Anyone have any suggestions?
Let say I'm working on an Excel clone in C#.
My grid is represented as follows:
private struct CellValue
{
private int column;
private int row;
private string text;
}
private List<CellValue> cellValues = new List<CellValue>();
Each time user add a text, I just package it as CellValue and add it into cellValues. Given a CellValue type, I can determine its row and column in O(1) time, which is great. However, given a column and a row, I need to loop through the entire cellValues to find which text is in that column and row, which is terribly slow. Also, given a text, I too need to loop through the entire thing. Is there any data structure where I can achive all 3 task in O(1) time?
Updated:
Looking through some of the answers, I don't think I had found one that I like. Can I:
Not keeping more than 2 copies of CellValue, in order to avoid sync-ing them. In C world I would have made nice use of pointers.
Rows and Columns can be dynamically added (Unlike Excel).
I would opt for a sparse array (a linked list of linked lists) to give maximum flexibility with minimum storage.
In this example, you have a linked list of rows with each element pointing to a linked list of cells in that row (you could reverse the cells and rows depending on your needs).
|
V
+-+ +---+ +---+
|1| -> |1.1| ----------> |1.3| -:
+-+ +---+ +---+
|
V
+-+ +---+
|7| ----------> |7.2| -:
+-+ +---+
|
=
Each row element has the row number in it and each cell element has a pointer to its row element, so that getting the row number from a cell is O(1).
Similarly, each cell element has its column number, making that O(1) as well.
There's no easy way to get O(1) for finding immediately the cell at a given row/column but a sparse array is as fast as it's going to get unless you pre-allocate information for every possible cell so that you can do index lookups on an array - this would be very wasteful in terms of storage.
One thing you could do is make one dimension non-sparse, such as making the columns the primary array (rather than linked list) and limiting them to 1,000 - this would make the column lookup indexed (fast), then a search on the sparse rows.
I don't think you can ever get O(1) for a text lookup simply because text can be duplicated in multiple cells (unlike row/column). I still believe the sparse array will be the fastest way to search for text, unless you maintain a sorted index of all text values in another array (again, that can make it faster but at the expense of copious amounts of memory).
I think you should use one of the indexed collections to make it work reasonably fast, the perfect one is the KeyedCollection
You need to create your own collection by extending this class. This way your object will still contain row and column (so you will not loose anything), but you will be able to search for them. Probably you will have to create a class encapsulating (row, column) and make it the key (so make it immutable and override equals and get hash code)
I'd create
Collection<Collection<CellValue>> rowCellValues = new Collection<Collection<CellValue>>();
and
Collection<Collection<CellValue>> columnCellValues = new Collection<Collection<CellValue>>();
The outer collection has one entry for each row or column, indexed by the row or column number, the inner collection has all the cells in that row or column. These collections should be populated as part of the process that creates new CellValue objects.
rowCellValues[newCellValue.Row].Add(newCellValue);
columnCellValues[newCellValue.Column].Add(newCellValue);
This smells of premature optimization.
That said, there's a few features of excel that are important in choosing a good structure.
First is that excel uses the cells in a moderately non-linear fashion. The process of resolving formulas involves traversing the spreadsheets in effectively random order. The structure will need a mechanism of easily looking up values of random keys cheaply, marking them dirty, resolved, or unresolvable due to circular reference. It will also need some way to know when there are no more unresolved cells left, so that it can stop working. Any solution that involves a linked list is probably sub-optimal for this, since they would require a linear scan to get those cells.
Another issue is that excel displays a range of cells at one time. This may seem trivial, and to a large extent it is, but It will certainly be ideal if the app can pull all of the data needed to draw a range of cells in one shot. part of this may be keeping track of the display height and width of the rows and columns, so that the display system can iterate over the range until the desired width and height of cells has been collected. The need to iterate in this manner may preclude the use of a hashing strategy for sparse storage of cells.
On top of that, there are some weaknesses of the representational model of spreadsheets that could be addressed much more effectively by taking a slightly different approach.
For example, column aggregates are sort of clunky. A column total is easy enough to implement in excel, but it has a sort of magic behavior that works most of the time but not all of the time. For instance, if you add a row into the aggregated area, further calculations on that aggregate may continue to work, or not, depending on how you added it. If you copy and insert a row (and replace the values) everything works fine, but if you cut and paste the cells one row down, things don't work out so well.
Given that the data is 2-dimensional, I would have a 2D array to hold it in.
Well, you could store them in three Dictionaries: two Dictionary<int,CellValue> objects for rows and columns, and one Dictionary<string,CellValue> for text. You'd have to keep all three carefully in sync though.
I'm not sure that I wouldn't just go with a big two-dimensional array though...
If it's an exact clone, then an array-backed list of CellValue[256] arrays. Excel has 256 columns, but a growable number of rows.
If rows and columns can be added "dynamically", then you shouldn't store the row/column as an numeric attribute of the cell, but rather as a reference to a row or column object.
Example:
private struct CellValue
{
private List<CellValue> _column;
private List<CellValue> _row;
private string text;
public List<CellValue> column {
get { return _column; }
set {
if(_column!=null) { _column.Remove(this); }
_column = value;
_column.Add(this);
}
}
public List<CellValue> row {
get { return _row; }
set {
if(_row!=null) { _row.Remove(this); }
_row = value;
_row.Add(this);
}
}
}
private List<List<CellValue>> MyRows = new List<List<CellValue>>;
private List<List<CellValue>> MyColumns = new List<List<CellValue>>;
Each Row and Column object is implemented as a List of the CellValue objects. These are unordered--the order of the cells in a particular Row does not correspond to the Column index, and vice-versa.
Each sheet has a List of Rows and a list of Columns, in order of the sheet (shown above as MyRows and MyColumns).
This will allow you to rearrange and insert new rows and columns without looping through and updating any cells.
Deleting a row should loop through the cells on the row and delete them from their respective columns before deleting the row itself. And vice-versa for columns.
To find a particular Row and Column, find the appropriate Row and Column objects, then find the CellValue that they contain in common.
Example:
public CellValue GetCell(int rowIndex, int colIndex) {
List<CellValue> row = MyRows[rowIndex];
List<CellValue> col = MyColumns[colIndex];
return row.Intersect(col)[0];
}
(I'm a little fuzzy on these Extension methods in .NET 3.5, but this should be in the ballpark.)
If I recall correctly, there was an article about how Visicalc did it, maybe in Byte Magazine in the early 80s. I believe it was a sparse array of some sort. But I think there were links both up-and-down and left-and-right, so that any given cell had a pointer to the cell above it (however many cells away that may be), below it, to the left of it, and to the right of it.