I am new to WinForms and am trying to make an application that will hold certain data in a DataGridView Style.
Now I've managed to create the app where if clicked on a cell the TextBoxes and the ComboBox get populated with data from the GridView, but I am now struggling to get the DateTimePicker to do the same...
Bellow you will see the code I am using to call the data back into the text and combo boxes, but I am missing the proper call for the DateTimePicker and I don't know how to call it properly
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
index = e.RowIndex;
DataGridViewRow row = dataGridView1.Rows[index];
textBox1.Text = row.Cells[0].Value.ToString();
dateTimePicker1.Value = row.Cells[1].Value.ToString();
comboBox1.Text = row.Cells[2].Value.ToString();
textBox2.Text = row.Cells[3].Value.ToString();
}
I am aware I can't convert type string to System.DateTime, but I don't know how I can do that...
Thank you in advance.
So to answer your question JohnG,
My current code adds a row of data into the Grid:
private void btnInsert_Click(object sender, EventArgs e)
{
dt.Rows.Add(textBox1.Text, dateTimePicker1.Value, comboBox1.Text, textBox2.Text);
}
The issue occurs when i add a new row, the date in the DateTime value cell updates for all rows, something i do not want.
Hope this helps.
Correction, it updates only the selected row, while it creates a new row.
In the example below, you may note that the code uses a DataSource to the grid. And I highly recommend you do the same regardless if the grid is initially empty and is only expecting data to be input by the user. In other words, it will simplify things in many-many ways.
So, if I have this correct, you have a grid, and some “external” text boxes, a date time picker and a combo box on a form. The columns in the grid have some values like name, date and some other values. And… is what you want is that when the user selects a cell/row in the grid… then… the “external” text boxes, date time picker and combo box will contain the same values as the selected row in the grid.
I will assume you want the user to be able to “edit” the text box and have it also update the grid’s cell and vice versa. If this is the case, then the example below uses this approach.
This is fairly trivial with a text box or date time picker… however a combo box is a little more work and you do not specify “how” you are currently adding items to the combo box. In other words, if the user is entering data into the grid and they get to cell 2 (the combo box value) you have the following code…
comboBox1.Text = row.Cells[2].Value.ToString();
This is a little odd in a sense that the value the user types into the cell will be “ignored” unless the value actually exists in the combo boxes list of items. In other words, this implies some pre-thought about what items are in the combo boxes list of items. If the grids cell 2 was itself a combo box cell, then this may change things, however, you would STILL have to do extra work to fill the combo boxes list of items.
Unfortunately, even with a combo box cell…, “binding” the combo boxes value from the grid to an “external” regular combo box is not as easy as it may appear. If the combo boxes list of items in the grid were simply strings AND the “external” regular combo boxes list of items was simply strings, then this is doable. However, this is rarely the case and it would still involve something similar to your current approach. Because of this, I will leave out the “external” combo box for now.
The example below shows how to “bind” the “external” TextBoxes and DateTimePicker. We will use a simple DataTable as a DataSource to the grid. In addition, we will also use this same DataTable as a DataSource when we “bind” the text boxes and date time picker. A better approach is to use a BindingSource and will update this later.
First let us get some test data for the grid. The grid will have four columns: First Name, Last Name, Hire Date and Department. The Hire Date is a column of type DateTime and all others are of a string type. The method below will return a DataTable showing the columns described above…
private DataTable GetDataForGrid() {
DataTable dt = new DataTable();
dt.Columns.Add("First Name", typeof(string));
dt.Columns.Add("Last Name", typeof(string));
dt.Columns.Add("Dept", typeof(string));
dt.Columns.Add("Hire Date", typeof(DateTime));
dt.Rows.Add("Joe", "Fowler", "Accounting", DateTime.Now);
dt.Rows.Add("Sally", "Field", "Human Resources", DateTime.Now.AddDays(1));
dt.Rows.Add("John", "Houston", "Management", DateTime.Now.AddDays(2));
dt.Rows.Add("Jack", "Black", "Contractor", DateTime.Now.AddDays(3));
dt.Rows.Add("Tony", "Curtis", "Production", DateTime.Now.AddDays(4));
return dt;
}
In the forms scope, we will create a global variable GridDT and set it using the method above. Then we will use the DataTable as a DataSource to the grid. The forms Load event may look something like…
DataTable GridDT;
private void Form1_Load(object sender, EventArgs e) {
GridDT = GetDataForGrid();
dataGridView1.DataSource = GridDT;
}
This should display the grid like below…
If you run this code, you will not see the text boxes and date time picker with the same values as the selected row. We still need to add the “binding” code for the controls like below…
private void Form1_Load(object sender, EventArgs e) {
GridDT = GetDataForGrid();
dataGridView1.DataSource = GridDT;
textBox1.DataBindings.Add("Text", GridDT, "First Name", true, DataSourceUpdateMode.OnPropertyChanged, "");
textBox2.DataBindings.Add("Text", GridDT, "Last Name", true, DataSourceUpdateMode.OnPropertyChanged, "");
dateTimePicker1.DataBindings.Add("Value", GridDT, "Hire Date", true, DataSourceUpdateMode.OnPropertyChanged, "");
}
The DataBindings.Add is self-explanatory, however the documentation can be found at Control.DataBindings Property
If you run the code now, you will note the text boxes change values when the user selects different rows in the grid. It should also be noted that this is a “slow” kind of a two-way binding. Example; If you change the “external” first name text box… then you will NOT see this change “immediately” in the grid. Depending on what control you click on, the change may not be reflected in the grid. If you click on another text box or press the Tab key… the change may not show in the grid.
The same idea applies to the grid. If the user changes the text in the first name cell, then those changes will NOT be “immediately” reflected into the “external” text box until the user selects a “different” ROW in the grid. If the user presses the Tab key or clicks on another cell in the SAME row… then the changes will NOT be reflected in the external text boxes.
This presents a little problem from a user stand point. IMO… I WANT to “see” those changes at least when the user either edits and leaves the cell in the grid OR if the user edits and leaves an “external” text box. To fix this, I will use a BindingSource instead of a DataTable as the DataSource.
After we switch to the BindingSource, you will not see much change and the previous problems persist. However, this will simplify our solution to the previous problem. 1) We will wire up the “external” text boxes Leave event to execute the BindingSources EndEdit() method to update the grid. 2) we will wire up the grids CellValueChanged event to do the same to update the external text boxes. 3) we will use the date time pickers Leave event to do the same.
Note I used the same Leave event for both text boxes. These final changes are below….
BindingSource GridBS;
private void Form1_Load(object sender, EventArgs e) {
textBox1.Leave += new EventHandler(textBox_Leave);
textBox2.Leave += new EventHandler(textBox_Leave);
dateTimePicker1.Leave += new EventHandler(textBox_Leave);
dataGridView1.CellValueChanged += new DataGridViewCellEventHandler(dataGridView1_CellValueChanged);
GridBS = new BindingSource();
GridBS.DataSource = GetDataForGrid();
dataGridView1.DataSource = GridBS;
textBox1.DataBindings.Add("Text", GridBS, "First Name", true, DataSourceUpdateMode.OnPropertyChanged, "");
textBox2.DataBindings.Add("Text", GridBS, "Last Name", true, DataSourceUpdateMode.OnPropertyChanged, "");
dateTimePicker1.DataBindings.Add("Value", GridBS, "Hire Date", true, DataSourceUpdateMode.OnPropertyChanged, "");
}
private void textBox_Leave(object sender, EventArgs e) {
GridBS.EndEdit();
}
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
GridBS.EndEdit();
}
I hope this makes sense and helps.
The type of DateTimePicker.Value is DateTime.
In order to initialize a DateTime object, you can use one of the following:
DateTime.Parse - Returns the DateTime. May throw an exception if the string format is not valid.
DateTime.TryParse - Updates a DateTime out parameter. Returns false if the string format is not valid.
However - since DateTimePicker.Value is a property, it cannot be passed as an out parameter. If you want to parse directly into it, use the Parse as you can see below. Alternatively you can use TryParse to parse into a local variable and then assign the propety to it.
Example using Parse:
string date_time_string; // initialized from your data grid or in any other way
try
{
dateTimePicker1.Value = DateTime.Parse(date_time_string);
}
catch (Exception ex)
{
// Handle parsing error
}
Following on JohnG's excellent comment, using binding not only solves the DateTime formatting issue; it provides the added benefit of inline editing using date/time pickers and combo boxes within the DataGridView itself.
If you'd like to try this approach, make a class to represent a Row in the view.
class Record
{
public string Description { get; set; } = $"Item {autoName++}";
public DateTime Date { get; set; } = DateTime.Now;
public CBValues Value { get; set; } = CBValues.Apple;
static char autoName = 'A';
}
enum CBValues
{
Apple,
Orange,
Banana,
}
Next make a BindingList<Record> of them...
BindingList<Record> Records = new BindingList<Record>();
and set it as the DataSource of your DataGridView in this initialization method which also installs the custom columns (the CalendarColumn class is copied from this Microsoft sample).
private void InitializeDataGridView()
{
dataGridView1.AllowUserToAddRows = false;
// Bind the list of records to the DataGridView
dataGridView1.DataSource = Records;
// Add one or more records to autogenerate the Columns
for (int i = 0; i < 3; i++) Records.Add(new Record());
// Swap autogenerated column for CalendarColumn.
dataGridView1.Columns.Remove(dataGridView1.Columns[nameof(Record.Date)]);
dataGridView1.Columns.Add(
// This class is copied from Microsoft example.
new CalendarColumn
{
Name = nameof(Record.Date),
DataPropertyName = nameof(Record.Date),
});
// Swap autogenerated column for DataGridViewComboboxColumn.
dataGridView1.Columns.Remove(dataGridView1.Columns[nameof(Record.Value)]);
dataGridView1.Columns.Add(
new DataGridViewComboBoxColumn
{
Name = nameof(Record.Value),
DataPropertyName = nameof(Record.Value),
DataSource = Enum.GetValues(typeof(CBValues)),
});
// Format columns
dataGridView1.Columns[nameof(Record.Description)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns[nameof(Record.Date)].Width = 150;
}
If you choose not to use inline editing, here's your handler code modified to use binding:
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
var record = Records[e.RowIndex];
textBox1.Text = record.Description;
dateTimePicker1.Value = record.Date;
comboBox1.SelectedIndex= (int)record.Value;
}
I have a combobox as below with 3 items ("abc", "abc" & "bbb").
The combobox also has AutoCompleteMode "SuggestAppend" & AutoCompleteSource "ListItems" properties. Now I want the text box to show the SelectedIndex of the combobox as below:
http://i.imgur.com/MJ4JdDN.png
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
this.textBox1.Text = this.comboBox1.SelectedIndex.ToString();
}
Everything seems fine until I select the 2nd "abc", the SelectedIndexChanged event will enter for the 1st time & display the index properly on the text box. But when the combobox lost focus, the SelectedIndexChanged event will fire again, causing the index to be displayed wrongly. I found that it only happens to items with the same value. Is there a way I can stop the event from firing twice?
http://i.imgur.com/gEw46xf.png
this.comboBox1.DataSource = dt;
this.comboBox1.DisplayMember = "Description"; //Some descriptive field to be shown in combobox
this.comboBox1.ValueMember = "Code"; //Unique code that user won't understand
this.comboBox1.SelectedIndex = -1;
put comboBox1_SelectedIndexChanged code in comboBox1_ValueChanged and there u'll get index in event arguments(sender i.e your comboBox cast it in combobox).
use comboBox1.SelectedIndex and u'll get the index.
In my C# WPF application (.NET 4.0) I have a DataGrid dynamically filled from code including a DataGridComboBoxColumn:
public static DataGridComboBoxColumn getCboCol(string colName, Binding textBinding)
{
List<string> statusItemsList = new StatusList();
DataGridComboBoxColumn cboColumn = new DataGridComboBoxColumn();
cboColumn.Header = colName;
cboColumn.SelectedItemBinding = textBinding;
cboColumn.ItemsSource = statusItemsList;
return cboColumn;
}
Using the BeginningEdit event different checks are performed.
If the checks return okay, I want to expand the combobox directly, otherwise edit mode is cancelled:
void dataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
...
if(notOK)
e.Cancel;
else {
DataGridComboBoxColumn dgCboCol = (DataGridComboBoxColumn)e.Column;
// expand dgCboCol
}
...
}
Questions: How to expand the combobox programmatically? Is BeginningEdit event the right place to do that?
Answer:
void dataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
if (e.EditingElement.GetType().Equals(typeof(ComboBox)))
{
ComboBox box = (ComboBox)e.EditingElement;
box.IsDropDownOpen = true;
}
}
Take a look at this
Try setting edit mode on the grid to a single click and then use the CellClick event to obtain the comboBox and expand it.
dataGrid.BeginEdit(true);
ComboBox comboBox = (ComboBox)dataGrid.EditingControl;
comboBox.IsDropDownOpen = true;
From DataGridBeginningEditEventArgs, you could access the generated element for the cell about to be edited like this:
var contentComboBox = e.Column.GetCellContent(e.Row) as ComboBox;
However, I'm not sure that this will get the actual ComboBox you need. DataGrids can generate two different elements for each cell, depending on whether they are in edit mode (read-only and read-write elements). Since BeginningEdit happens just before entering edit mode, this will get the read-only element.
The better event to handle this in would probably be PreparingCellForEdit, which will fire after BeginEdit is actually called on the data item (in other words, if BeginningEdit was not canceled). In that event, you can access the element directly through the EditingElement property.
I have a form in C# that uses a ComboBox.
How do I prevent a user from manually inputting text in the ComboBox in C#?
this.comboBoxType.Font = new System.Drawing.Font("Arial", 15.75F);
this.comboBoxType.FormattingEnabled = true;
this.comboBoxType.Items.AddRange(new object[] {
"a",
"b",
"c"});
this.comboBoxType.Location = new System.Drawing.Point(742, 364);
this.comboBoxType.Name = "comboBoxType";
this.comboBoxType.Size = new System.Drawing.Size(89, 32);
this.comboBoxType.TabIndex = 57;
I want A B C to be the only options.
Just set your combo as a DropDownList:
this.comboBoxType.DropDownStyle = ComboBoxStyle.DropDownList;
I believe you want to set the DropDownStyle to DropDownList.
this.comboBoxType.DropDownStyle =
System.Windows.Forms.ComboBoxStyle.DropDownList;
Alternatively, you can do this from the WinForms designer by selecting the control, going to the Properties Window, and changing the "DropDownStyle" property to "DropDownList".
You can suppress handling of the key press by adding e.Handled = true to the control's KeyPress event:
private void Combo1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
}
I like to keep the ability to manually insert stuff, but limit the selected items to what's in the list.
I'd add this event to the ComboBox. As long as you get the SelectedItem and not the Text, you get the correct predefined items; a, b and c.
private void cbx_LostFocus(object sender, EventArgs e)
{
if (!(sender is ComboBox cbx)) return;
int i;
cbx.SelectedIndex = (i = cbx.FindString(cbx.Text)) >= 0 ? i : 0;
}
Why use ComboBox then?
C# has a control called Listbox. Technically a ComboBox's difference on a Listbox is that a ComboBox can receive input, so if it's not the control you need then i suggest you use ListBox
Listbox Consumption guide here: C# ListBox
I've a very trivial requirement which makes me go nuts. I've a DataGridView in windows forms application. This contains one databound ComboBox Column. I'm using DisplayMember and ValueMember properties of that combobox.
Now my requirement is ComboBox should show the list of DisplayMembers in drop down list but when user selects one item from it, I should display the part of that DisplayMember in the combobox cell visible to the user. For example.
My display member list looks as below:
"Cust1 - Customer 1"
"Cust2 - Customer 2"
"Cust3 - Customer 3"
and when user selects any one of them from the above list (Say user selected 'Cust2 - Customer 2') then I need to display the value in the combobox column cell as only "Cust2" instead of complete DisplayMember text.
This DisplayMember list is a combination of two fields from the datasource bound to it i.e. First part points to CustomerCode field and second part points Customer name. I need to display only CustomerCode in the ComboBox cell after user selects one item from the drop down list.
How can I do this? Or should I come up with my own control which will have a different AutoCompleteCustomSource and display member value. Even I'm confused with that approach too.
Update: As no one has come up with any solution to my problem. Now I'm starting a bounty for that, also if anyone can suggest me other way to implement the same functionality, it would be great.
I've even tried to come up with my own control and tried to work on simple combobox to display a different value than the selected dropdown list, even that didn't work. Is there any other way to implement this? Any tips and tricks are greatly appreciable.
#Anurag: Here is the code which I've used.
Created a datagridview in the design mode. Created one column of type 'DataGridViewComboBoxColumn' that and named it as CustomerColumn.
In the designer file it looks like below:
private System.Windows.Forms.DataGridViewComboBoxColumn CustomerColumn;
This is the entity class which I've used for datasource
public class Customer
{
public int Id { get; set; }
public string CustCode { get; set; }
public string CustName { get; set; }
public string NameWithCode { get; set; }// CustCode - CustName format
}
In the form load event I'm doing the following:
CustomerColumn.DataSource = GetCustomers();
CustomerColumn.DisplayMember = "NameWithCode";
CustomerColumn.ValueMember = "Id";
I'm answering my own question because I've implemented my own solution to this by using custom control.
This custom control is created by keeping a textbox above combo box in such a way that only drop down button of combobox is visible.
Now I've created a custom column in datagridview deriving the DataGridViewEditingControl from my usercontrol.
I've added a property in Usercontrol which will take drop down list source from the control which is hosting datagridview.
Now in the EditingControlShowing event I'm setting this property as below
private void dataGridView2_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if(dataGridView2.CurrentCell.ColumnIndex.Equals(0) && e.Control is UserControl1)
{
var uscontrol = e.Control as UserControl1;
uscontrol.DropDownListSource = source;
}
}
This drop down list source is used in the usercontrol to set the autocompletesource to the textbox and datasource to the combobox as below:
Whenever I set the DropDownDataSource I'm firing an event in the usercontrol which will do the following. This is to ensure that every time EditingControlShowing event occurs for this column in DataGridView, this source is updated for textbox and combobox in usercontrol.
private void DropDownSourceChanged(object sender, EventArgs eventArgs)
{
textBox1.AutoCompleteCustomSource = DropDownListSource;
textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
comboBox1.DataSource = DropDownListSource;
}
Now whenever user starts typing in the textbox autocomplete source will display dropdown list with 'NameWithCode' values and if user selects one of them then I'll set it to the Text propery overidden in my usercontrol which will be used for the cell value in the DataGridView. Now based on the Textbox text (which is NameWithCode) I can get the code part and set it to the text property.
If user uses combobox dropdown button to select the item then I'll get the combobox selected text and set it in the Textbox which is ultimately used by the cell for getting value.
This way I could achieve the solution I want.
#Homam, solution also works but when I change the ComboBox's DropDownStyle to allow the user to type the value in the combobox it behaves weirdly and not getting up to the mark solution for my requirement. Hence I used this solution.
I know that this is not perfect solution, but I looked for a better one and I didn't find, so I went to a workaround
I did the following:
when the user open the ComboBox, I change the DisplayMember to "NameWithCode"
when the user close it, I return it to "CustCode"
You can Access to the ComboBox control by DataGridView.EditingControlShowing event for the DataGridView.
The code:
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
var comboBox = e.Control as ComboBox;
comboBox.DropDown += (s1, e1) => comboBox.DisplayMember = "NameWithCode";
comboBox.DropDownClosed += (s2, e2) =>
{
// save the last selected item to return it after
// reassign the Display Member
var selectedItem = comboBox.SelectedItem;
comboBox.DisplayMember = "CustCode";
comboBox.SelectedItem = selectedItem;
};
}
Note: You have to start the DisplayMember with "CustCode"
Good luck!
Each time at the offensive of event dataGridView1_EditingControlShowing there is addition of new handlers for events comboBox.DropDown and comboBox.DropDownClosed. It results in the increase of number of these handlers and their repeated calls. This code decides this problem.
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
var comboBox = e.Control as ComboBox;
comboBox.DropDown += comboBox_DropDown;
comboBox.DropDownClosed += comboBox_DropDownClosed;
}
private void comboBox_DropDown(object sender, System.EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox != null)
{
comboBox.DropDown -= comboBox_DropDown;
comboBox.DisplayMember = "NameWithCode";
}
}
private void comboBox_DropDownClosed(object sender, System.EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox != null)
{
comboBox.DropDownClosed -= comboBox_DropDownClosed;
var selectedItem = comboBox.SelectedItem;
comboBox.DisplayMember = "CustCode";
comboBox.SelectedItem = selectedItem;
}
}