I have an IDictionary which holds an ever changing list of my class and I want to display the collection in a winforms UI probably using DataGridView or another control. I plan to have timer that refreshes the table every 1 to 2 seconds and a pause button. when any given row is clicked I need to be able to get the class from the dictionary or return the first field which be the key in the IDictionary.
So I create the IDictionary thus:
public static IDictionary<string, AClass> aList = new Dictionary<string, AClass>();
AClass is a simple collection of strings:
public class AClass
{
public string data1{ get; set; }
public string data2{ get; set; }
}
I add the AClass to the IDictionary thus:
if (!MainForm.aircraftList.ContainsKey(strMyData))
{
MainForm.aList[strMyData] = new AClass{ data1= strMyData};
}
How can I create the table with all the columns from AClass which are around 12 and rows from the IDictionary aList of which there is a variable number hovering around 100.
To possibly help you get started, below is code that uses a DataGridView to display data using a DataTable; a List<AClass> objects and also using a Dictionary<string, AClass>. As I said using a dictionary, you will have to do some extra steps to get the AClass Value from the dictionary. These extra steps indicate that the Dictionary is not necessarily the best data structure to use as a DataSource if there a multiple variables in your class.
The code below uses a DataGridView and four (4) buttons. There are buttons to display data into the DataGridView using a DataTable; a List<AClass> (added a key), and a Dictionary<string, AClass> and finally a button to clear the DataGridView
Hope this helps.
public Dictionary<string, AClass> DGVDictionary;
public DataTable DGVTable;
public List<AClass> DGVList;
public Form1() {
InitializeComponent();
}
private Dictionary<string, AClass> GetDictionary() {
Dictionary<string, AClass> dictionary = new Dictionary<string, AClass>();
for (int key = 0; key < 15; key++) {
AClass ac = new AClass();
ac.data1 = "data1" + key;
ac.data2 = "data2" + key;
dictionary.Add(key.ToString(), ac);
}
return dictionary;
}
private void CreateTable() {
DGVTable = new DataTable();
DGVTable.Columns.Add("key", typeof(int));
DGVTable.Columns.Add("data1", typeof(string));
DGVTable.Columns.Add("data2", typeof(string));
}
private void FillDataTable() {
for (int key = 0; key < 15; key++) {
AClass ac = new AClass();
ac.data1 = "data1" + key;
ac.data2 = "data2" + key;
DGVTable.Rows.Add(key, ac.data1, ac.data2);
}
}
private List<AClass> FillList() {
List<AClass> list = new List<AClass>();
for (int key = 0; key < 15; key++) {
AClass ac = new AClass();
ac.key = key;
ac.data1 = "data1" + key;
ac.data2 = "data2" + key;
list.Add(ac);
}
return list;
}
private void buttonDataTable_Click(object sender, EventArgs e) {
CreateTable();
FillDataTable();
dataGridView1.DataSource = DGVTable;
MessageBox.Show("DGV with a DataTable");
}
private void buttonList_Click(object sender, EventArgs e) {
DGVList = FillList();
dataGridView1.DataSource = DGVList;
MessageBox.Show("DGV with a List<AClass>");
}
private void buttonDictionary_Click(object sender, EventArgs e) {
DGVDictionary = GetDictionary();
dataGridView1.DataSource = DGVDictionary.ToList();
MessageBox.Show("DGV with a Dictionat<string, AClass>");
}
private void buttonClear_Click(object sender, EventArgs e) {
dataGridView1.DataSource = null;
}
Since you are using an IDictionary, the default binding mechanism will not be able to retrieve the individual entries nor the properties on the class AClass. You can create a custom BindingSource to handle these tasks.
The primary responsibility of custom BingSource is to supply a collection of ProperyDescriptors for the AClass type. These are retrieved using the TypeDescriptor.GetProperties Method. The BindingSource also needs to access the underlying DataSource items by index; this is handled in the indexer of the BindingSource.
To use this BindingSource, create an instance of it passing your IDictionary instance and then assign this BindingSource to the DataGridView's DataSource property.
internal class myBindingSource : BindingSource
{
private IDictionary<string, AClass> source;
private System.ComponentModel.PropertyDescriptorCollection props;
public myBindingSource(IDictionary<string, AClass> source)
{
this.source = source;
props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(AClass));
this.DataSource = source;
}
public override System.ComponentModel.PropertyDescriptorCollection GetItemProperties(System.ComponentModel.PropertyDescriptor[] listAccessors)
{
return props;
}
public override object this[int index]
{
get {return this.source.Values.ElementAtOrDefault(index);}
set {}
}
public override bool AllowNew
{
get { return false; }
set{}
}
public override bool AllowEdit
{
get { return false; }
}
public override bool AllowRemove
{
get {return false;}
}
public override int Count
{
get {return ((this.source == null) ? -1 : this.source.Count);}
}
}
Edit: I have added an override of the Count property to the code to allow the the BindingSource.ResetBindings Method to work properly when called to force the bound control to re-read the values from the BindingSource. I have also updated the indexer code.
If you modify the custom BindingSource's underlying DataSource after assigning it as the DataGridView's DataSource, you will need to call the BindingSource.ResetBindings method for those changes to be reflected in the grid.
For Example:
private IDictionary<string, AClass> aList;
private myBindingSource bs;
public Form1()
{
InitializeComponent();
aList = new Dictionary<string, AClass>();
bs = new myBindingSource(aList);
dgv.DataSource = bs;
aList.Add("1", new AClass());
aList.Add("2", new AClass { data1 = "AA", data2 = "BB" });
bs.ResetBindings(false);
}
Related
I have a Database which has a table name CourseOffered that looks like this:
enter image description here
The only column i want to select (request to be returned) is Semester and add it to a dropdown list. Here's my code in the Repository Class.
public List<CoursesOffered> GetSemester()
{
List<CoursesOffered> SemestersName = null;
try
{
string sql = "SELECT DISTINCT Semester FROM CoursesOffered";
DataTable dt = _idac.GetManyRowsCols(sql, null);
SemestersName = DBList.ToList<CoursesOffered>(dt);
}
catch (Exception)
{
throw;
}
return SemestersName;
}
I'm calling GetManyRowsCols() from my DataAccess Class and I have a DBList and Entity Class that looks like this.
class DBList
{
public static List<T> ToList<T>(DataTable dt)
where T : IEntity, new()
{
List<T> TList = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T tp = new T();
tp.SetFields(dr);
TList.Add(tp);
}
return TList;
}
}
public class EntityBase : IEntity
{
public void SetFields(DataRow dr)
{
// use reflection to set the fields from DataRow
Type tp = this.GetType();
foreach (PropertyInfo pi in tp.GetProperties())
{
if (null != pi && pi.CanWrite)
{
string nm = pi.PropertyType.Name.ToUpper();
if (nm.IndexOf("ENTITY") >= 0)
// In LINQ to SQL Classes, last properties are links to other tables
break;
if (pi.PropertyType.Name.ToUpper() != "BINARY")
pi.SetValue(this, dr[pi.Name], null);
}
}
}
}
This is how i'm trying to add it to the Drop down List,
private void CourseManagement_Load_1(object sender, EventArgs e)
{
try
{
List<CoursesOffered> semesterList = _ibusinessSemester.GetSemester();
cbCourseNumberDropDown.DataSource = semesterList;
cbCourseNumberDropDown.Refresh();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
but i keep getting an error that says Column 'CourseNum' Does not belong to the table
Based on your description, you want to convert one column to list.
Since I don't have the code about IEntity, I can not test your code.
However, I make the following code to make it.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var list = DBList.ToList<string>(table, "Semeter").Distinct().ToList();// the code I have modified
comboBox1.DataSource = list;
}
DataTable table = new DataTable();
private void Form1_Load(object sender, EventArgs e)
{
table.Columns.Add("Course",typeof(string));
table.Columns.Add("Semeter", typeof(string));
table.Columns.Add("MaxEncrollment", typeof(int));
table.Rows.Add("CPSC 410","Fall 2016",35);
table.Rows.Add("CPSC 411", "Fall 2016", 30);
table.Rows.Add("CPSC 440", "Fall 2016", 35);
table.Rows.Add("Math301", "Fall 2016", 35);
}
}
class DBList
{
public static List<T> ToList<T>(DataTable dt,string columnname)
{
List<T> TList = new List<T>();
TList = dt.AsEnumerable().Select(r => r.Field<T>(columnname)).ToList();
return TList;
}
}
I have a DataGridView whose DataSource is a DataTable with five columns. If I attempt to access a column's ReadOnly property, like so:
datagridview.Columns[1].ReadOnly = true;
It throws a NullReferenceExcpetion.
I understand this is due to how the framework manages its auto generated columns, as noted by the answer to this question.
My question is: How do I make a column(s) readonly when the data source is auto generated?
Can't really say why it's not working, but a simple test with this code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = GenerateData();
dataGridView1.Columns[0].ReadOnly = true;
}
private List<DataSourceTest> GenerateData()
{
return new List<DataSourceTest>()
{
new DataSourceTest(1, "A"),
new DataSourceTest(2, "B"),
new DataSourceTest(3, "C"),
new DataSourceTest(4, "D"),
new DataSourceTest(5, "E"),
new DataSourceTest(6, "F"),
};
}
}
public class DataSourceTest
{
public DataSourceTest(int id, string name) { ID = id; Name = name; }
public int ID { get; set; }
public string Name { get; set; }
}
and making the gridview EditMode set to EditOnEnter so we can easily check if it's readonly or not, shows that it does the job well.
But if you still have issues, the best bet is to use an event, and the closest event for your question is the DataBindingComplete that will fire after the binding is done, so on that time, you will have full access to all your columns as they already bind to the gridview object.
double click on the event in the GridView control and add your readonly setter:
private void dataGridView1_DataBindingComplete(
object sender, DataGridViewBindingCompleteEventArgs e)
{
dataGridView1.Columns[0].ReadOnly = true;
}
In true TEK fashion, I figured out a solution to my own question:
To do this, you need to make use of the ColumnAdded event
datagridview.ColumnAdded += dataGridView_ColumnAdded;
Then in the event, you can check a column by name:
private void dataGridView_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
if (e.Column is DataGridViewColumn)
{
DataGridViewColumn column = e.Column as DataGridViewColumn;
column.ReadOnly = true;
if (column.Name == "first_name")
{
column.ReadOnly = false;
}
}
}
Make column read-only when column has been generated
private void Form1_Load(object sender, EventArgs e)
{
List<Student> allStudent = new List<Student>();
for (int i = 0; i < 10; i++)
{
allStudent.Add(new Student { Name = "Student" + i, Roll = i + 1 });
}
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = allStudent;
//Edited to show column count
MessageBox.Show("Column count is " + dataGridView1.Columns.Count);
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
column.ReadOnly = true;
}
}
public partial class Student
{
public string Name { get; set; }
public int Roll { get; set; }
}
I have a List of string type in which I am storing some values and then inserting them into the database. Now I want to show those values into the Gridview when I fill my GridView data source with that List it only shows the total number of characters of each value in the list. Is there any procedure that I show the list values into the DataGridView.
My code below is as follows:
private void GenerateButton_Click(object sender, EventArgs e)
{
List<string> SerialNumberList = new List<string>();
int SerialNumberStart = Convert.ToInt32(Regex.Replace(StartSerialBox.Text, "[^0-9]+", string.Empty));
int SerialLimit = Convert.ToInt32(LimitBox.Text);
for (int i = 0; i < SerialLimit;i++ )
{
SerialNumberStart++;
SerialNumberList.Add("S" + SerialNumberStart);
}
for (int j = 0; j < SerialNumberList.Count;j++ )
{
Adapter.insertserialnumbers(SerialNumberList[j] , DateTime.Now.ToString()); //Insertiona Procedure which will save the values into the database.
}
SerialGridView.DataSource = SerialNumberList ;
}
you can use BindingList
for example :
protected void GenerateButton_Click(object sender, EventArgs e)
{
BindingList<ListType> SerialNumberList = new BindingList<ListType>();
int x = 5;
SerialNumberList.Add(new ListType(x.ToString()));
grid.DataSource = SerialNumberList;
grid.DataBind();
}
}
public class ListType
{
public ListType()
{ }
private string Itemname;
public ListType(string _ListItem)
{
ListItem = _ListItem;
}
public string ListItem
{
get { return Itemname; }
set { Itemname = value; }
}
}
You can add extention method for IEnumerable(here List) type object from this SO question. Use this extention method to create DataTable from SerialNumberList and bind to DGV like this:
SerialGridView.DataSource = SerialNumberList.ToDataTable();
I need to bind an array of dynamically created textboxes to a string[] or List<string>. This was the closest WinForm Controls binding to a List<T> problem but no cigar.
Typically for single textboxes I bind the Textboxes' Text property:
Engine engine = new Engine();
public frmMain()
{
InitializeComponent();
txtQuery.DataBindings.Add("Text",engine,"Query");
}
To a class property:
public class Engine : IEngine, INotifyPropertyChanged
{
private string query;
public string Query
{
get { return query; }
set
{
query = value;
InvokePropertyChanged(new PropertyChangedEventArgs("Query"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
I've given it a go with an array of textboxes and strings without luck:
Front End:
TextBox[] txtBoxArr = new TextBox[numberOfDimensions];
for (int i = 0; i < numberOfDimensions;i++)
{
string tabName = "Dataset" + (i + 1);
tabCtrlDatasets.TabPages.Add(tabName,tabName);
txtBoxArr[i] = new TextBox();
txtBoxArr[i].Name = "txtDataset" + i ;
txtBoxArr[i].DataBindings.Add("Text",engine,"Dataset");
tabCtrlDatasets.TabPages[i].Controls.Add(txtBoxArr[i]);
}
Back End:
private string[] dataset;
public string[] Dataset
{
get { return dataset; }
set
{
dataset = value;
InvokePropertyChanged(new PropertyChangedEventArgs("Dataset"));
}
}
To get it working I need to know the index of the item in the array. I cant recall doing this before, does anyone know how to identify the index of the textbox to bind it to the correct item in the string array?
I'm a bit tired today and having a memory block.
Instead of a string array for holding the TextBox values, you could use a DataTable with a single row. In this way, you don't have to increase the complexity of your Engine class (as it will still have a single property) and you can bind the textboxes to the engine.DataTable's DataColumnCollection (mapping them by the column's index).
Try this in the form:
Engine engine = new Engine();
public Form1()
{
InitializeComponent();
var dt = new DataTable();
dt.Columns.Add("column0");
dt.Columns.Add("column1");
dt.Columns.Add("column2");
dt.Rows.Add("abc", DateTime.Now, 123456, 789.0123F);
engine.DataTable = dt;
int numberOfDimensions = engine.DataTable.Columns.Count;
TextBox[] txtBoxArr = new TextBox[numberOfDimensions];
for (int i = 0; i < numberOfDimensions; i++)
{
string tabName = "Dataset" + (i + 1);
tabCtrlDatasets.TabPages.Add(tabName, tabName);
txtBoxArr[i] = new TextBox();
txtBoxArr[i].Name = engine.DataTable.Columns[i].ColumnName;
txtBoxArr[i].DataBindings.Add("Text",
engine.DataTable, txtBoxArr[i].Name);
tabCtrlDatasets.TabPages[i].Controls.Add(txtBoxArr[i]);
}
}
and modify the Engine class like this:
private DataTable dataTable;
public DataTable DataTable
{
get
{
return dataTable;
}
set
{
dataTable = value;
InvokePropertyChanged(new PropertyChangedEventArgs("DataTable"));
}
}
I Have dataGrid like that:
public class myGrid : DataGrid
{
DataTable Table = new DataTable();
public myGrid()
{
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
List<string> List = new List<string> { "Size1", "Size2", "Price", "Price2", "Note"} ;
foreach (string Name in List)
{
Table.Columns.Add(Name);
DataGridTextColumn c = new DataGridTextColumn();
c.Header = Name;
c.Binding = new Binding(Table.Columns[Name].ColumnName);
this.Columns.Add(c);
}
DataColumn[] keys = new DataColumn[1];
keys[0] = Table.Columns["PRICE"];
Table.PrimaryKey = keys;
this.DataContext = Table;
}
public void AddRow(object[] Values)
{
Table.LoadDataRow(Values, true);
}
}
After AddRow is called, Table does have a row, but myGrid doesn't.
What am i doing wrong?
Thanks!
Using MVVM will be a better apporach.... you will not even nedd to inherit the grid for this purpose...
View Model
class MyViewModel:INotifyPropertyChanged
{
private ObservableColleciton<string> myCollection;
public MyViewModel()
{
//FunctiontoFillCollection()
}
public ObservableColleciton<string> MyCollection
{
get { return myCollection;}
set
{
mycolletion = value;
// i am leaving implemenation of INotifyPropertyChanged on you
// google it.. :)
OnpropertyChanged("MyCollection");
}
}
}
View.Xaml
<DataGrid ItemsSource={Binding Path=MyCollection}>
<!--Make Columns according to you-->
</DataGrid>
View.xaml.cs
/// <summary>
/// Interaction logic for MainView.xaml
/// </summary>
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
Now add ne thing to MyColleciton and it will be reflectied in the View automatically.....
read some article fro MVVm implemenation for better understanding...
Make Table a public property:
private DataTable m_Table
public DataTable Table
{
get { return this.m_Table; }
protected set { m_Table = value; NotifyPropertyChanged("Table"); }
}
You also need to call NotifyPropertyChanged("Table"); in your AddRow function.