WPF iterate through datagrid - c#

Using WPF C#.NET4.5 using visual studio 2012 ulti.
Old winforms code:
foreach (DataGridViewRow paretoRow in ParetoGrid.Rows)
{
if ((Convert.ToInt32(paretoRow.Cells["CurrentPareto"].Value) < (Convert.ToInt32(paretoRow.Cells["NewPareto"].Value))))
{
paretoRow.Cells["pNew"].Value = downArrow
}
}
As you can see each row I cycle through I check a specific cell, if true I then populate another cell. This was good old winforms code I used many times before...however.
Switching over to WPF was alot more different than i previously assumed.
DataGrid does not contain the Row property. Instead, I think you need to use:
DataGridRow paretoRow in paretogrid.Items
But im still at a loss on who to now get the cell.
So my question is, is there syntax changes to perform, if so where? Or as I'm beginning to believe datagrids in WPF operate with Objects more so than winforms thus not needing to use a propertie called "row", if this is the case what logic/syntax should i know use in this example?
Thanks for your patience guys, think when I go home for the bank holiday I'll do a bit of WPF digging to see how different it actually is.

People seem to be overcomplicating this, this worked for me:
foreach (System.Data.DataRowView dr in yourDataGrid.ItemsSource)
{
MessageBox.Show(dr[0].ToString());
}

I think first think you want to do is to get all rows of your DataGrid:
public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
if (null != row) yield return row;
}
}
and then iterate through your grid:
var rows = GetDataGridRows(nameofyordatagrid);
foreach (DataGridRow row in rows)
{
DataRowView rowView = (DataRowView)row.Item;
foreach (DataGridColumn column in nameofyordatagrid.Columns)
{
if (column.GetCellContent(row) is TextBlock)
{
TextBlock cellContent = column.GetCellContent(row) as TextBlock;
MessageBox.Show(cellContent.Text);
}
}

Yes, you are right. WPF DataGrid is built around better supporting the use of objects.
You could use a ViewModel similar to the following. Build them all into a collection and then set that collection as your ItemsSource. You would also need to use a ValueConverter if you want to display and image instead of a checkmark for pNew being true/false.
public class FooViewModel : INotifyPropertyChanged
{
private int currentPareto;
public int CurrentPareto
{
get
{
return currentPareto;
}
set
{
if (currentPareto == value)
return;
currentPareto = value;
OnPropertyChanged("CurrentPareto");
OnPropertyChanged("pNew");
}
}
private int newPareto;
public int NewPareto
{
get
{
return newPareto;
}
set
{
if (newPareto == value)
return;
newPareto = value;
OnPropertyChanged("NewPareto");
OnPropertyChanged("pNew");
}
}
public bool pNew
{
get
{
return CurrentPareto < NewPareto;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Edit
To simplify it a little, you could use a base ViewModel class and use PropertyChanged weaving. The code would simplify to this:
public class FooViewModel : ViewModelBase
{
public int CurrentPareto { get; set; }
public int NewPareto { get; set; }
public bool pNew { get { return CurrentPareto < NewPareto; } }
}

I don't even understand why is it just so complicated to get rows and their values in a datagrid. It feels like hell finding how. The api even give funny funny event names which is not so direct to the point also. Why can't just people concentrate on the baseline and give what exactly is needed and not all sorts of different options with no use and sense at all. I mean to eat all you need is a spoon and fork right. Never even changed since 100,000 years ago. This is my code thanks to the guy who mentioned some people just try to over-complicate things and waste your time.
private void dtaResultGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ActivateTestDatagridAccess();
}
public async void ActivateTestDatagridAccess()
{
try
{
await Task.Delay(500);
foreach (System.Data.DataRowView dr in dtaResultGrid.ItemsSource)
{
for (int j = 0; j < dtaResultGrid.Columns.Count; j++)
{
Console.WriteLine(dr[j].ToString());
}
Console.Write(Environment.NewLine);
}
}
catch (Exception exrr)
{
Console.WriteLine(exrr.ToString());
}
}

The 'simplest' answer, from Charles, did it for me. But I used Items instead of ItemsSource.
Now, for people getting this error:
System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.Data.DataRowView'.
What did it for me was disabling the DataGrid's property CanUserAddRows. This removes the placeholder line for a new line, thus removing the placeholder object (which is NOT a DataRowView, but something else). If you already have this disabled, then I don't know.
Since I wanted to loop through each element of each row, I added another foreach:
foreach (System.Data.DataRowView dr in nameofyourgrid.Items)
{
foreach (var item in dr.Row.ItemArray)
{
MessageBox.Show(item.ToString());
}
}

In WPF you go about it a lot more dynamic and ObjectOrientated. You can bind the Column "pNew" on a Property of the element you put in the DataGrid, which returns downarrow.
If the value changes you can raise the Event PropertyChanged (Interface INotifyPropertyChanged) and the bound Property will get reevaluated.
Also interesting for beginning with WPF is DataTemplate, ControlTemplate, Converter.
Converter changes the Property Value to a usable Value for WPF (e.g. BoolToVisibility) when the Property gets called.
DataTemplate and ControlTemplate can be used to alter the appearance of the Control.
There are several good Tutorials for WPF out there. I would also recommend to look into the MVVM-Pattern to use as a between layer of your Businessobject and your WPF-Control, especially to handle things like what you try to do here.

if you fill your datagridview rows using an instance of a class (like struct_class)
this would be the fastest way to have a foreach loop
foreach (struct_class row in dgv.Items)
{
MessageBox.Show(row.name);
}

Why can't you just use this property to get the number of rows and then use a For loop to iterate through?
dataGridView1.Rows.Count

Related

getrowcellvalue get the value but not fill in textbox using object

I'm using gridcontrol of devexpres. I want to fill an object like textedit,datepicker,checkbox etc. from the gridcontrol, for that I had to make a function like DGSearch_CellClick; but there is a problem when I get the value, can't set it in objects like textedit,datepicker,checkbox.
Below is my code:
public void DGSearch_CellClick(GridView GView, object[] ConMast, int CurrRow)
{
try
{
for (int i = 0; i <= ConMast.Length - 1; i++)
{
ConMast[i] = GView.GetRowCellValue(CurrRow, GView.Columns[i]).ToString();
}
}
catch (Exception) { }
}
public void Search(int k)
{
try
{
// vbcls.DGSearch_CellClick(GViewSearch, new Control[] { TxtMstID, DtDate, TxtJno, DtSite, TxtPartyCode, TxtCompanyCode, TxtTypeCode, TxtArticalCode, TxtJanCharniCode, TxtProcessCode, TxtRgPer, TxtPoPer, TxtManufacturerSize, TxtLsWt, TxtMainCutno, TxtCutNo, TxtRemarks, ChkAutoCut, ChkAutoKno }, k);
vbcls.DGSearch_CellClick(GViewSearch, new object[] { TxtMstID.Text, DtDate.EditValue, TxtJno.Text, DtSite.EditValue, TxtPartyCode.Text, TxtCompanyCode.Text, TxtTypeCode.Text, TxtArticalCode.Text, TxtJanCharniCode.Text, TxtProcessCode.Text, TxtRgPer.Text, TxtPoPer.Text, TxtManufacturerSize.Text, TxtLsWt.Text, TxtMainCutno.Text, TxtCutNo.Text, TxtRemarks.Text, ChkAutoCut.Checked, ChkAutoKno.Checked }, k);
FillData();
GCSearch.Visible = false;
BtnEdit.Focus();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
While you certainly can edit values directly within a grid, I think your best bet is to make your grid databound, to a DataTable if you have to, but preferably to a collection of objects.
The addition of a BindingSource component will give you one more tool, and I recomment its use. For example, if you bind your collection to your binding source and bind your binding source to the grid, then any changes to the focused row in the grid will automatically update bindSource.Current.
Here is what that might look like:
gridDgSearch.DataSource = bindDgSearch;
List<DgSearch> dgSearchList = DgSearchCrud.GetAll();
bindDgSearch = dgSearchList;
Now, rather than trying to find items in the grid, simply iterate through your collection. Rather than updating items in the grid, update them in the collection and refresh the grid.
Also, e.RowHandle in many of the grid events will indicate the row in question, meaning this:
(DgSearch)gridDgSearchView.GetRow(e.RowHandle);
Will give you the entire object for that row (if the event arg doesn't already).
If you can elaborate more on where the contents of the grid come from, what you want to do with it, etc, it might help narrow down how you can handle it with this approach.

Adding properties to single DatagridViewCell

I'm on a project where base control is a DatagridView, in there is displaying numbers of production quantities, whish every row is a bach on the production process and every column is a type of product in the bash.
Every Bash has a time to acomplish to finalize the process, when time is ended, the cell in the row must be colored, then the user has the ability to add more time if needed to every single product.
So my proposal was adding to every Cell object two properties
State of the bash product (int).
Extended time in minutes (int 0 default).
So I create my own DataGridViewCell this way
public class PedidosCell : DataGridViewCell
{
private int _estado;
private int _tiempo;
public int Estado
{
get { return _estado; }
set { _estado = value; }
}
public int TiempoExtra
{
get { return _tiempo; }
set { _tiempo = value; }
}
}
After that I created the colum that uses PedidosCell
public class PedidosColumn : DataGridViewColumn
{
public PedidosColumn()
: base(new PedidosCell())
{
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
// Ensure that the cell used for the template is a PedidosCell.
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(PedidosCell)))
{
throw new InvalidCastException("Must be a PedidosCell");
}
base.CellTemplate = value;
}
}
The problem starts here because if i call a constructor
PedidosColumn col = new PedidosColumn();
the propertie
col.CellTemplate.TiempoExtra
doesn't exist; AND it's obvious because the overrider CellTemplate is returning the original CellTemplate
But how can i do it (if possible) to make a simple dgView.Row[0].Cell[2].TiempoExtra
or
dgView.Row[0].Cell[2].Estado to get the information I need to know how the cell will be colorated?
Thank's For the help
Why you not use the property Tag that's every rows has to storage
the batch information, than you can retrieve easily
structure BatchInfo{
//===>Informacion de tu batch aqui.
//===>Add here fields of information of your batch
...
}
//===>You can fill each datagrid row tag property with the batch info like this
foreach(DataGridViewRow iRow int miDataGrid.Rows){
iRow.Tag = new BatchInfo("BatchName");//===>Create a new object of your structure
}
/===>If you want to retrieve the batchInfo from the row tag property you need to do it like this way
//===>You can not assign the the value directly because tag property is an object, so you need to do a cast like this way below
BatchInfo SelectedBatchInfo = (BatchInfo)miDataGrid.SelectedRows(0).Tag;
//==>And if you want add color to specific cell do it this way
miDataGrid.SelectedRow(0).Cell("MiColumna").style.BackColor = Color.Navy;
miDataGrid.SelectedRow(0).Cell("MiColumna").style.Forecolor = Color.WhiteSmoke;
If you already extended the DataGrid Class why you don't just add a new property to it like this
BatchInfo GetSelectedBatchInfo{
get{
if(this.SelectedRows.Count > 0){
return (BatchInfo)this.SelectedRows(0).Tag;
}else{
return null;
}
}
}

Add many rows at once

I'm just starting out with WPF and MVVM framework. I have a Window with telerik RadGridView and I would like to add data from multipul rows in the same time. Has anyone got any advice or examples ,I've tried numerous ways but none seem to work out.
Thank you
My ViewModel
private IList<Ligne> _CurrentLigne;
public IList<Ligne> CurrentLigne
{
get { return _CurrentLigne; }
set
{
_CurrentLigne= value;
OnPropertyChanged("CurrentLigne");
}
}
var _ligne = Currentligne as Ligne;
foreach (Ligne ligne in CurrentLigne)
{
if (Currentligne!= null)
_ligneBLL.InsetLigne(ligne);
}
My View
<telerik:RadGridView x:Name="GridView"
AutoGenerateColumns="False"
ItemsSource="{Binding ListeLigne}"
SelectedItem="{Binding CurrentLigne, Mode=TwoWay}"
SelectionMode="Multiple" >
Try This !!
foreach (Ligne ligne in ListLigne)
{
var _ligne = ligne as Ligne;
_ligneBLL.InsetLigne(ligne);
}
I recommend that you read the Data Binding Overview page on MSDN so that you can get a better idea on data binding. For now, I can give you a few tips. Firstly, in WPF, your property should really have used an ObservableCollection<T>, like this:
private ObservableCollection<Ligne> _ListeLigne = new ObservableCollection<Ligne>();
public ObservableCollection<Ligne> ListeLigne
{
get { return _ListeLigne; }
set
{
_ListeLigne = value;
OnPropertyChanged("ListeLigne");
}
}
Then your selected item like this:
private Ligne _CurrentLigne = new Ligne();
public Ligne CurrentLigne
{
get { return _CurrentLigne; }
set
{
_CurrentLigne= value;
OnPropertyChanged("CurrentLigne");
}
}
With properties like this, your XAML would be fine. Lastly, to add your items, you simply do this:
ListeLigne = new ObservableCollection<Ligne>(SomeMethodGettingYourData());
Or just...:
ListeLigne = SomeMethodGettingYourData();
... if your data access method returns an ObservableCollection<Ligne>. If you want to select a particular element in the UI, then you must select an actual item from the data bound collection, but you can do that easily using LinQ.
using System.Linq;
CurrentLigne = ListeLigne.First(l => l.SomeLigneProperty == someValue);
Or just:
CurrentLigne = ListeLigne.ElementAt(someValidIndexInCollection);
Oh... and I've got one other tip for you. In your code:
foreach (Ligne ligne in CurrentLigne)
{
if (Currentligne!= null) // this is a pointless if condition
_ligneBLL.InsetLigne(ligne);
}
The above if condition is pointless because the program execution will never enter the foreach loop if the collection is null.
I think you want to use a BindingList. It is the list I always use, but remember you will need to post your notifyChange events.

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Why ItemContainerGenerator.ContainerFromIndex() returns null and how to avoid this behavior?

I'm using this snippet to analyze the rows I've selected on a datagrid.
for (int i = 0; i < dgDetalle.Items.Count; i++)
{
DataGridRow row = (DataGridRow)dgDetalle.ItemContainerGenerator.ContainerFromIndex(i);
FrameworkElement cellContent = dgDetalle.Columns[0].GetCellContent(row);
// ... code ...
}
The cycle runs smoothly, but when processing certain indexes, the second line throws a null exception. MSDN's documentation says that ItemContainerGenerator.ContainerFromIndex(i) will return null if 'if the item is not realized', but this doesn't help me to guess how could I get the desired value.
How can I scan all the rows? Is there any other way?
UPDATE
I'm using this snippet to read a CheckBox as explained here. So I can't use binding or ItemSource at all unless I change a lot of things. And I cannot. I'm doing code maintenance.
Try this,
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
The DataGrid is virtualizing the items, the respective rows (i.e. containers) are only created when the row is in view.
You could either turn off virtualization (which makes the first time loading very slow if you have many items, also the memory usage will be higher) or you just iterate over the data and check the values of the data objects' properties which should be bound to the data-grid. Usually you should not need the UI elements at all...
Use this subscription:
TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
TheListBox.Dispatcher.Invoke(() =>
{
var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
if (TheOne != null)
// Use The One
});
};
In addition to other answers: items aren't available in constructor of the control class (page / window / etc).
If you want to access them after created, use Loaded event:
public partial class MyUserControl : UserControl
{
public MyUserControl(int[] values)
{
InitializeComponent();
this.MyItemsControl.ItemsSource = values;
Loaded += (s, e) =>
{
for (int i = 0; i < this.MyItemsControl.Items.Count; ++i)
{
// this.MyItemsControl.ItemContainerGenerator.ContainerFromIndex(i)
}
};
}
}
In my case grid.UpdateLayout(); didn't help an I needed a DoEvents() instead:
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
WPFTools.DoEvents();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
/// <summary>
/// WPF DoEvents
/// Source: https://stackoverflow.com/a/11899439/1574221
/// </summary>
public static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(
delegate (object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}), frame);
Dispatcher.PushFrame(frame);
}

Categories

Resources