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;
}
}
}
Related
I have a WinForms Dialog with 2 ListBox controls. In the application under test, doubleClicking any items in one of the listbox controls (I'll call this the CONTROL LISTBOX) results in selecting the matching item in the other listbox (SLAVE LISTBOX).
My test causes multiple entries to be made in the CONTROL LISTBOX. The test then performs a ListBox.SelectedItem.DoubleClick() on each of the CONTROL lISTBOX items, comparing the ListBox.SelectedItemText from both listbox controls.
In the application UI, this ALWAYS works, but the test of the call to ListBox.SelectedItemText for SLAVE LISTBOX returns the text matching what is slected in the UI correctly ONLY on the initial iteration of the doubleclick\compare.
Can anybody help me figure out what I'm doing wrong? Thanks!
Here is my code:
public bool SelectMainEventViaErrorEvent(int eventIdx)
{
bool bSuccess = false;
errorEvents.Items.Select(eventIdx);
System.Threading.Thread.Sleep(1000);
errorEvents.Items.SelectedItem.DoubleClick();
System.Threading.Thread.Sleep(1000);
if (eventIdx > 0)
{
IVScrollBar vertScroll = mainEvents.ScrollBars.Vertical;
vertScroll.ScrollDownLarge();
}
if (errorEvents.SelectedItemText == mainEvents.SelectedItemText)
{
bSuccess = true;
}
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected error event = {errorEvents.SelectedItemText}");
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected main event = {mainEvents.SelectedItemText}");
return bSuccess;
}
As you can see, by the following image, the text in both list boxes are identical. However, the call to ListBox.SelectedItemText for the top listbox (SLAVE LISTBOX) returns the value from the first iteration, which matched the first item in the bottom listbox (CONTROL LISTBOX) during the first iteration of the doubleclick/compare.
Proof that the text of the selected listbox items match
Comparing with plain text is bad idea since "Text" != "Text ". What you could use in yor case is DisplayMember and ValueMember properties.
I will demonstrate it for you with manually populating listboxes but you do it from database or however you do it.
First of all create class that will store your values and it's ID's. I usually create it like this (so i am able to use that class later for something else)
public class Int_String
{
public int _int { get; set; } // Important to be declared like properties and not like variables
public string _string { get; set; }
}
Now let's populate our listbox like this:
public YourForm()
{
List<Int_String> list = new List<Int_String>();
list.Add(new Int_String { _int = 1, _string = "Some text" }); // I am populating it manually but you will populate it from DB or somewhere else
list.Add(new Int_String { _int = 2, _string = "Some other text" });
list.Add(new Int_String { _int = 3, _string = "One more text" });
// Now when we have list we need to bind it to our listbox.
// IMPORTANT!!!!!
// Display member and Value member properties SHOULD be able to be added before and after assigning datasource to control (like combobox) BUT for some reason on listbox it only works when you assign it AFTER you bind your datasource to listbox.
// If you ever work with other controls and use these values, ALWAYS declare display member and value member BEFORE you bind datasource. Why? For now let's just say it is much faster but explanation is for other question
myListBox1.DataSource = list;
myListBox1.DisplayMember = "_string"; // When you start typing .DisplayMember, you will not get that property in recommendation since it is hidden so do not think there is not that property there.
myListBox1.ValueMember = "_int"; // Same as above
}
Now when you populate listbox like this and second one on same way with same id's you could simply do if(listbox1.SelectedValue == listbox2.SelectedValue) and compare them even if their text is not equal but id is.
BONUS:
Also what you can do is expand class like this:
public class Int_String
{
public int _int { get; set; }
public string _string { get; set; }
public string SomethingOther = "AsD";
public bool IsTrue()
{
return true;
}
}
then bind it on same way and do this:
Int_String item = listbox1.SelectedItem as Int_String;
bool check = item.IsTrue();
MessageBox.Show(item.SomethingOther);
So basically you bind whole class for each item in listbox, display to user one of the variables (in our case _string), set ValueMember to other unique variable so it is easy to search whole listbox and when needed get whole class from that item.
I wasn't ever able to get this working by iterating forward through the errorEvent listbox in my automated test code, but it DOES work when iterating backwards through the errorEvent listbox.
Calling code:
for (int i = eventViewer.GetErrorEventsCount() - 1; i >= 0; i--)
{
bResult = eventViewer.SelectMainEventViaErrorEvent(i);
if (!bResult)
{
break;
}
System.Threading.Thread.Sleep(2000);
}
Verification Code:
public bool SelectMainEventViaErrorEvent(int eventIdx)
{
bool bSuccess = false;
DisableToolTips(true);
errorEvents.Select(eventIdx);
errorEvents.SelectedItem.DoubleClick();
System.Threading.Thread.Sleep(1000);
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected error event = {errorEvents.SelectedItemText}");
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected main event = {mainEvents.SelectedItemText}");
if (errorEvents.SelectedItemText == mainEvents.SelectedItemText)
{
bSuccess = true;
}
return bSuccess;
}
I have 2 forms: 1 form contains a listView and another form contains a comboBox.
I would like the first column of the listView to be loaded into the comboBox on the second form.
This is my attempt:
comboBox1.Items.Add(Form2.listView2.columnHeader1);
However, this does not work. (Form2.ListView is inaccessible due to its protection level). Suggestions would be appreciated.
Quick and dirty solution:
Go to the Properties of listView2 in the winforms designer and look for Modifiers. Then select Public like you see in the picture below:
And now it will be accessible
The more elegant solution:
Create a property in your first Form which has only a getter. In this getter you can safely return the columnHeader1:
public ColumnHeader ColumnHeader { get { return this.listView1.Columns["columnHeader1"]; }}
or:
public ColumnHeader ColumnHeader { get { return this.columnHeader1; }}
EDIT:
It seems that you rather would like to have all values from that column. So in this case you would have to return all the values, which can be done like this:
public List<string> AlllValuesFromColumn
{
get
{
int indexOfColumn = listView2.Columns.IndexOf(this.columnHeader1);
return listView2.Items.OfType<ListViewItem>().Select(x => x.SubItems[indexOfColumn].Text).ToList();
}
}
To Add all the values in one blow to the ComboBox you can use AddRange:
comboBox1.Items.AddRange(Form2.AlllValuesFromColumn.ToArray());
EDIT 2:
But the solution I personally would prefer is to hold the data source in an extra variable. This can be passed around. No magic there.
Make a public method in your second form and let the method set the comboboxitems.
Form1:
bool Do = true;
int i = 0;
Form2 F = new Form2();
while (Do)
{
try
{
F.AddItem(listView1.Columns[i].Name);
i++;
}
catch
{
Do = false;
}
}
Form2:
public void AddItem(string ToAdd)
{
comboBox1.Items.Add(ToAdd);
}
How do i enable edit mode only for the certain column?
Let's say i have this columns:
Product Code || Quantity || Description || Price
I wanted to enable edit mode only for Quantity column and Price column. The rest cannot be edited.
I already did this below code, but it seems not working:
dataGridView1.EditMode = DataGridViewEditMode.EditOnKeystroke;
dataGridView1.Columns["Quantity"].ReadOnly = false;
dataGridView1.Columns["Price"].ReadOnly = false;
All i can do is disable ReadOnly to false, but it is enabled all of the columns that i don't want to enable them.
Note: By the time program runs for the first time, i already set the ReadOnly to true
The out-of-box solution is using attributes on your data model:
public class MyDataModel
{
// Do not show in data grid view
[System.ComponentModel.Browsable(false)]
public virtual int ID { get; protected set; }
// Set to read-only in data grid view
[System.ComponentModel.ReadOnly(true)]
public virtual string Person { get; set; }
}
this will help you
MSDN DataGridViewColumn
You should set ReadOnly = true to all non-editable column.
By default, column are set to ReadOnly = false
foreach (DataGridViewColumn dc in dgvTestParameter.Columns)
{
if (dc.Index.Equals(0) || dc.Index.Equals(4))
{
dc.ReadOnly = false;
}
else
{
dc.ReadOnly = true;
}
}
int EditableCol_index = datagridview["colname"].index;
// if this is true , you wont be able to edit even you set the column readonly=false;
datagridview1.readonly = false;
foreach (datagridviewcolumns c in datagridview1 )
{
c.readonly = true;
if( c.index == EditableCol_index )
{
c.readonly = false; // editable
}
}
From design part you just check "Enable Editing" mode in your Datagridview.
this example may help you,
I have added two columns on as readonly purpose(column name is "ReadOnly Column") and another as editable column(column name is "Normal Column").
Code:
dataGridView1.Columns["readOnlyColumn"].ReadOnly = true;
Thank you....
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
I have a method that adds items to my listbox called refreshInterface which is called as soon as the programe starts, adding names of homeforms in the listbox using the FormItems class, here is the rereshInterface method below
public void refreshInterface()
{
//int number = 0;
foreach (DataSet1.xspGetAnalysisUsageTypesRow homeForms in myDataSet.xspGetAnalysisUsageTypes)
{
var forms = new FormItems(homeForms);
listBox1.Items.Add(forms);
}
}
The FormItems class is this below
public class FormItems
{
public DataSet1.xspGetAnalysisUsageTypesRow types { get; set; }
public FormItems(DataSet1.xspGetAnalysisUsageTypesRow usageTypes)
{
types = usageTypes;
}
public override string ToString()
{
// returns the rows that are relating to types.xlib_ID
var libtyps = types.GetxAnalysisUsageRows();
var cnt = 0;
foreach (DataSet1.xAnalysisUsageRow ty in libtyps)
{
//returns true if ty is null
bool typeNull = ty.Isxanu_DefaultNull();
// if its false, if xanu_Default is set
if (!typeNull)
{
cnt += 1;
}
}
var ret = String.Format("set {0} [Set: {1}]", types.xlib_Desc, cnt);
//return this.types.xlib_Desc;
return ret;
}
}
Each listbox (the listbox is on the left of the homeform) item has a number of reports that can be added to it, so for instance, i select an homeform from my listbox, there are 12 textboxes on the right hand side and each textbox has a pair of buttons which are Browse and Clear. If I click on the browse button a new form appears, and i select a report from that form and add it to a particular textbox, the count for that homeform should update, and i clear a textbox for a particular homeform, the count should also update.
At the moment when i debug the application, it shows me the count of each Homeform depending on the amount of reports added to the homeform, but while the programe is running, if i add a new report to a homeform, the count does not update until i restart the debug session. I was told about using a Databinding method but not sure of how i could use it here
How do i ge my listbox item to update ?
You should probably look into binding. Here is a good place to start:
http://www.codeproject.com/Articles/140621/WPF-Tutorial-Concept-Binding
If you want a GUI to respond to data changes then binding is your best friend.
You should bind List Box component source to Observable Collection, every update you do to Observable Collection will update List Box data.
Might not be exact but should give you an idea.
public void refreshInterface()
{
Dictionary<int,string> items = new Dictionary<int,string>();
//int number = 0;
foreach (DataSet1.xspGetAnalysisUsageTypesRow homeForms in myDataSet.xspGetAnalysisUsageTypes)
{
var formitem = new FormItems(homeForms);
items.Add(formitem.someprop, formitem.toString());
}
listbox.DataSource = items;
listbox.DisplayMember = "Value";
listbox.ValueMember = "Key";
}