I have problem with copying one row from my datagrid and then copy it.
For multiple selected rows it works fine as i want. But for one row not.
This is code for copy row
/// <summary>
/// Method which will copy entire row.
/// </summary>
private void CopyRow(object obj)
{
var datagrid = obj as System.Windows.Controls.DataGrid;
List<string> valsCollection = new List<string>();
foreach (ENotifiedTruck item in datagrid.SelectedItems)
{
valsCollection.Add(item.ToStringLP());
}
var rows = GetDataGridRows(datagrid);
int i1 = 0;
foreach (DataGridRow r in rows)
{
DataGridColumn column = datagrid.Columns[0];
TextBlock cellcontent = column.GetCellContent(r) as TextBlock;
valsCollection[i1] = string.Format("{0}\t{1}", cellcontent.Text, valsCollection[i1]);
i1++;
}
datagrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
valsCollection.Insert(0, string.Empty);
ApplicationCommands.Copy.Execute(null, datagrid);
string oldresult = (string)Clipboard.GetData(DataFormats.Text);
List<string> rowCollection = new List<string>();
rowCollection = oldresult.Split(new char[] { '\n' }).ToList<string>();
if (rowCollection.Count == 0)
return;
string last = rowCollection[0];
rowCollection = new List<string>();
rowCollection.Add(last);
rowCollection.AddRange(valsCollection);
oldresult = string.Join("\n", rowCollection);
Clipboard.SetText($"{oldresult}");
}
public IEnumerable<DataGridRow> GetDataGridRows(System.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(item);
if (null != row) yield return row;
}
}
Now in this particular line :
valsCollection[i1] = string.Format("{0}\t{1}", cellcontent.Text, valsCollection[i1]);
I got error when i select only one row.
This is the message i get from error: "An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll"
Any ideas? Thanks :]
DataGridRow has an Item property that should be the ENotifiedTruck you are trying to use.
You can use that instead of creating a separate collection beforehand.
Also, from what it looks like, you are getting all DataGridRow objects from GetDataGridRows based on the ItemsSource collection, while you are only creating entries in the valsCollection for selected rows.
If you have 1 selected row and 2 or more rows, then I think this should fail with an out of range exception when you get to [1] and valsCollection has no [1].
if(!datagrid.SelectedItems.Contains(r.Item))
continue;
will skip any rows that aren't selected.
Related
I have a datagrid where the user can select multiple cells of a single column in the grid, I want to iterate through the selected cells and retrieve the value of the first column of the selected cells' row, but I'm already stuck in the first step of the foreach loop. I did this before with datagridview in WinForms but it seems WPF is different.
foreach (DataGridCell cell in AppraiseeDataGrid.SelectedCells)//It says it cant convert type datagridcellinfo to datagricell
DataGrid.SelectedCells returns an DataGridCellInfo in WPF:
foreach (DataGridCellInfo cell in AppraiseeDataGrid.SelectedCells)
{
DataGridColumn column = cell.Column;
object dataRowItem = cell.Item;
}
You have to iterate through all of it's items and determine if the cell is selected, check this code:
foreach (DataGridCell cell in AppraiseeDataGrid.Items)
{
if (cell.IsSelected == true)
{
// Type your code here
}
}
string firstSelectedCellText = GetCellText(DataGrid.SelectedCells.First());
private string GetCellText(DataGridCellInfo currentcellInfo)
{
var currentCell = GetDataGridCell(currentcellInfo);
if (currentCell == null) return string.Empty;
int itemIndex = DataGrid.Items.IndexOf(currentcellInfo.Item);
DataGrid.CurrentCell = new DataGridCellInfo(DataGrid.Items[itemIndex], currentcellInfo.Column);
if (currentCell.Content is TextBox textBox)
{
return textBox.Text;
}
if (currentCell.Content is TextBlock textBlock)
{
return textBlock.Text;
}
return string.Empty;
}
private DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
{
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent != null)
return (DataGridCell)cellContent.Parent;
return null;
}
How can I preserve columns' sorting in DataGrid after changing its ItemsSource?
The following code preserve sorting but it doesn't set column headers of DataGrid to the "sorted" state (so there's no "sorting" icon and that stuff):
SortDescriptionCollection sortDescriptions = new SortDescriptionCollection();
foreach (SortDescription sd in OccupationsDataGrid.Items.SortDescriptions)
{
sortDescriptions.Add(sd);
}
OccupationsDataGrid.ItemsSource = q;
foreach (SortDescription sd in sortDescriptions)
{
OccupationsDataGrid.Items.SortDescriptions.Add(sd);
}
Using Telerik's JustDecompile and looking at the DataGrid.
In the static constructor for DataGrid, we have this line:
ItemsControl.ItemsSourceProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(null, new CoerceValueCallback(DataGrid.OnCoerceItemsSourceProperty)));
So DataGrid.OnCoerceItemsSourceProperty is called when the ItemsSource changes. It is defined as this:
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
DataGrid dataGrid = (DataGrid)d;
if (baseValue != dataGrid._cachedItemsSource && dataGrid._cachedItemsSource != null)
{
dataGrid.ClearSortDescriptionsOnItemsSourceChange();
}
return baseValue;
}
It ends up calling ClearSortDescriptionsOnItemsSourceChange. Which is this:
private void ClearSortDescriptionsOnItemsSourceChange()
{
base.Items.SortDescriptions.Clear();
this._sortingStarted = false;
List<int> groupingSortDescriptionIndices = this.GroupingSortDescriptionIndices;
if (groupingSortDescriptionIndices != null)
{
groupingSortDescriptionIndices.Clear();
}
foreach (DataGridColumn column in this.Columns)
{
column.SortDirection = null;
}
}
It appears that the column's SortDirection is being wiped out and that must control the appearance of the sort arrows. So... we should put it back when we add our sort descriptions back. Change the loop that re-adds the SortDescriptions to this:
foreach (SortDescription sd in sortDescriptions)
{
OccupationsDataGrid.Items.SortDescriptions.Add(sd);
foreach (var col in OccupationsDataGrid.Columns.Where(aa => aa.SortMemberPath == sd.PropertyName))
{
col.SortDirection = sd.Direction;
}
}
I'm trying to iterate through my RadGridView rows, but when I have more than 20 or 30 items, the loop doesn't get all rows.
For example: using this code in a radgridview with 5 items, I can get all of them and do whatever I want, but when my grid has more than 20 items, it gets only 10 rows. Is this a bug or something like that? How can I solve it?
Here's my code:
private List<object> ReturnListFounds(string text)
{
List<object> a = new List<object>();
foreach (var item in myGrid.Items)
{
if (item == null)
continue;
GridViewRow row = myGrid.ItemContainerGenerator.ContainerFromItem(item) as GridViewRow;
if (row == null)
continue;
foreach (GridViewCell cell in row.Cells)
{
if (cell != null && cell.Value != null)
{
string str = cell.Value.ToString();
if (str.Equals(text, StringComparison.InvariantCultureIgnoreCase) || str.ToLower().Contains(text.ToLower()))
{
a.Add(row.Item);
break;
}
}
}
}
return a;
}
#Edit
I found out the problem. The thing is: the method "ItemContainerGenerator.ContainerFromItem(item) as GridViewRow" returns null if the item is outside of the view area. But I'm using this method in a grid containing 123 items and I can only get the row for the 20 first items.
I need to be able to get all of the items, not just the ones in the view area. I have already tried to set the virtualization false (EnableRowVirtualization = false; EnableColumnVirtualization = false;), but it didin't work as well.
Is there a way of getting all of the rows using this method?
Have you tried this?
var rows = StrategyGridView.ChildrenOfType<GridViewRow>();
It works fine for me. Hope it helps!
I tried a lot of things to make this work and I found one. It's not the best way of doing this, but it works. I anyone has anything better, just post here! Share with us!
private List<object> ReturnListFounds(string text)
{
List<object> result = new List<object>();
for (int l = 0; l <= Items.Count; l++)
{
var cell = new GridViewCellInfo(this.Items[l], this.Columns[0], this);
if (cell.Item != null)
{
var props = cell.Item.GetType().GetProperties();
foreach (var p in props)
{
if (p == null || cell.Item == null)
continue;
var t = p.GetValue(cell.Item);
if (t == null)
continue;
var str = t.ToString();
if (str.Equals(text, StringComparison.InvariantCultureIgnoreCase) || str.ToLower().Contains(text))
{
result.Add(cell.Item);
}
}
}
}
result = new List<object>(result.Distinct());
return result;
}
I have a method that stores each line in a gridview into the database, then if the save is successful, removes the row; but if it isn't successful (cannot be stored in the db) it does not remove the row. Unfortunately, I can't get the row-removal to work properly.
This is my current code:
public static void SavePAC(PlantAreaCode_CreateView CView)
{
List<int> removeRows = new List<int>();
// For each cell in the DataGrid, stores the information in a string.
for (rows = 0; rows < CView.dataGridView1.Rows.Count; rows++)
{
correctSave = false;
if (CView.dataGridView1.Rows[rows].Cells[col].Value != null)
{
// Creates a model, then populates each field from the cells in the table.
PModel = new PlantAreaCode_Model();
PModel.AreaCode = Convert.ToString(CView.dataGridView1.Rows[rows].Cells[0].Value);
PModel.AreaName = Convert.ToString(CView.dataGridView1.Rows[rows].Cells[1].Value);
PModel.Comments = Convert.ToString(CView.dataGridView1.Rows[rows].Cells[2].Value);
// Passes the model into the Database.
Database_Facade.Operation_Switch(OPWRITE);
}
if (correctSave == true) // correctSave is set in the database insert method.
{
removeRows.Add(rows);
}
}
foreach (int i in removeRows)
{
CView.dataGridView1.Rows.RemoveAt(0); // Deletes all bar the last row, including any rows that cause errors
}
}
I have also tried:
foreach (int i in removeRows)
{
CView.dataGridView1.Rows.RemoveAt(i);
}
But that crashes at halfway, because the Rows index keeps changing each time a row is removed.
How can I achieve this? How can I remove a row if the save is successful, but keep it if there is an error?
May this help:
1] Make sure correctSave is being modified correctly.
2] Revert the loop flow, Looping backward allow to remove the row processed by the loop without affecting the index of the next row to process.
for (rows = CView.dgvCreate.Rows.Count - 1; rows >= 0 ; rows--)
3] Use CView.dataGridView1.Rows.RemoveAt(rows);
Try to populate collection of rows for removing with DataGridViewRow not with index. This works for me.
public void SavePAC(PlantAreaCode_CreateView CView)
{
List<DataGridViewRow> removeRows = new List<DataGridViewRow>();
foreach (DataGridViewRow row in CView.dataGridView1.Rows)
{
correctSave = false;
if (row.Cells[col].Value != null)
{
// Creates a model, then populates each field from the cells in the table.
PModel = new PlantAreaCode_Model();
PModel.AreaCode = Convert.ToString(row.Cells[0].Value);
PModel.AreaName = Convert.ToString(row.Cells[1].Value);
PModel.Comments = Convert.ToString(row.Cells[2].Value);
// Passes the model into the Database.
Database_Facade.Operation_Switch(OPWRITE);
}
if (correctSave == true) // correctSave is set in the database insert method.
{
removeRows.Add(row);
}
}
foreach (DataGridViewRow rowToRemove in removeRows)
{
CView.dataGridView1.Rows.Remove(rowToRemove);
}
}
You have to sort removeRows in descending order.
List<int> removeRowsDesc = removeRows.OrderByDescending(i => i);
Then use the foreach loop
foreach (int i in removeRowsDesc)
{
CView.dataGridView1.Rows.RemoveAt(i);
}
This way the reindexing wont affect the deletion.
I have written a C# code to read data from a csv file. The data is in the form say:
2,3,4,5,6
4,2,4,5,6
4,5,6,3,2
5,3,5,6,3
The code to read it is:
var lines = File.ReadLines("Data.csv");
var numbers = ProcessRawNumbers(lines);
The function ProcessRawNumbers is as follows:
private static List<List<double>> ProcessRawNumbers(IEnumerable<string> lines)
{
var numbers = new List<List<double>>();
/*System.Threading.Tasks.*/
Parallel.ForEach(lines, line =>
{
lock (numbers)
{
numbers.Add(ProcessLine(line));
}
});
return numbers;
}
private static List<double> ProcessLine(string line)
{
var list = new List<double>();
foreach (var s in line.Split(Separators, StringSplitOptions.RemoveEmptyEntries))
{
double i;
if (Double.TryParse(s, out i))
{
list.Add(i);
}
}
return list;
}
I would like to do the same with DataGridView. How can this be achieved?
In DataGridView I give input as follows:
Also, is it possible to have the number of columns change dynamically?
Data entered in a DataGridView is stored in its rows and cells. To exctract the data, you have to manually iterate over the rows and cell:
public List<string[]> ExtractGridData(DataGridView grid)
{
int numCols = grid.Columns.Count;
List<string[]> list = new List<string[]>();
foreach (DataGridViewRow row in grid.Rows)
{
if (row.IsNewRow) // skip the new row
continue;
string[] cellsData = new string[numCols];
foreach (DataGridViewCell cell in row.Cells)
if (cell.Value != null)
cellsData[cell.ColumnIndex] = cell.Value.ToString();
list.Add(cellsData);
}
return list;
}
If you want to change the columns dynamically, you can access the Columns property of the grid. For example, to add a column you can write:
dataGridView1.Columns.Add("NewColumn", "New Column");
Also note that using Parallel.ForEach in your scenario has no advantage, because you have to process the data sequentially, and by using lock statement, you forced the sequential processing. So there is no parallel proccessing.