C# Datagridview - Convert TextColumn to ComboBoxColumn - c#

I have a windows forms application containing a datagridview control. The datagridview is populated by the contents of an xml file. At the moment, all of the columns are displayed as datagridviewtextboxcolumns. I want to select one that is populated by a particular xml tag and display it's content in a datagridviewcomboboxcolumn along with 2 other options.
EXAMPLE:
<SMS>
<Number>+447931663542</Number>
<DateTime>2009-07-12T17:00:02</DateTime>
<Message>YES</Message>
<FollowedUpBy>Unassigned</FollowedUpBy>
<Outcome>Resolved</Outcome>
</SMS>
The OUTCOME tag is the column that I would like to be displayed as a comboboxcolumn in the datagridview. If for example the tag is empty and contains no data, then I want to display nothing, but have the comboboxcolumn populated with 3 possible options to choose from (Unresolved, Resolved, Pending). If however the tag contains data, I want that particular item to be displayed in the comboboxcolumn, and have the other two options available to be selected.
Help in achieving this would be appreciated greatly!
Regards,
EDIT:
Currently I use this code:
colOutcome = new DataGridViewComboBoxColumn();
colOutcome.HeaderText = "Outcome";
colOutcome.Width = 90;
colOutcome.Items.AddRange("Resolved", "Unresolved", "Pending");
this.dataGridView1.Columns.Insert(1, colOutcome);
this.dataGridView1.Columns[1].Name = "OutcomeColumn";
This code above populates the combobox. THE PROBLEM IS: When The xml document populates the datagridview, the outcome column just appears as a textbox column, containing the data inbetween the outcome tags in the xml file. My point is, how can i get the datagridview to realise when it reads the outcome column that it needs to be changed into a combobox column and then display the data that way, along with the other potentially selectable options in the combobox?! Currently the datagridview gets populated with all columns as textboxcolumns containing the data, as well as a seperate combobox column which is not what I want. I need the application to merge the outcome column and its data with the code above.
Any ideas?

Updated Answer
You could pass in the XML document to a function that will loop through each node and determine whether it should be a ComboBox one or not i.e. if the name is "Outcome".
private void CreateColumns(XmlDocument doc)
{
foreach (...) // loop through each node in xml document
{
if (node.Name == "Outcome")
{
var items = new List<string>() { "Resolved", "Unresolved", "Pending" };
this.dataGridView1.Columns.Add(CreateComboBoxColumn(node.Name, items));
}
else
{
this.dataGridView1.Columns.Add(String.Format("col{0}", node.Name), node.Name);
}
}
}
Then your code for creating the Outcome column would be:
private DataGridViewComboBoxColumn CreateComboBoxColumn(string colHeaderText, List<string> items)
{
var colOutcome = new DataGridViewComboBoxColumn();
colOutcome.HeaderText = colHeaderText;
colOutcome.Width = 90;
colOutcome.Items.AddRange(items.ToArray());
colOutcome.Name = String.Format("col{0}", colHeaderText);
return colOutcome;
}
You would then just call CreateColumns on the form load event and pass in your XML. You should only need to create the columns once.
My advice would be to have a similar function that will find all the SMS elements and add a new row populating it with the information in each node.
public void MyForm_Load(object sender, EventArgs e)
{
var doc = new XmlDocument(filename);
CreateColumns(doc);
CreateRows(doc);
}
Hope that helps.

Answer #2 for me, based on the updated question.
The problem you are experiencing is with the AutoGeneratedColumns functionality of the DataGridView. You will need to create your columns manually before databinding. This can be done at design-time or run-time. I prefer design-time because it gives you a bit more direction with the look/feel of the grid but either way works.
You will need to disable the AutoGeneratedColumns property of the grid:
private void Form1_Load(object sender, EventArgs e)
{
// Define your columns at run-time here if that's what you prefer
this.dataGridView1.AutoGeneratedColumns = false;
this.dataGridView1.DataSource = myDataSource;
}

I'm not sitting in front of VS so this might not compile but should give you direction.
You need to either pre-populate the ResolvedColumn with the 3-4 possible values at design-time or assign it to another datasource at runtime. If you chose the design-time approach, simply open the DataGridView "Edit Columns" dialog, find the ResolvedColumn, go to Items, and add your values ("", "Unresolved", "Pending", "Resolved"). The empty value might help the ComboBox to render if there is the possiblity of rendering the grid with SMS records that have no Outcome.
To bind the possible options at runtime do something like this:
private List<string> _outcomeDataSource;
private void Form1_Load(object sender, EventArgs e)
{
_outcomeDataSource = new List<string>;
_outcomeDataSource.Add("");
_outcomeDataSource.Add("Unresolved");
_outcomeDataSource.Add("Pending");
_outcomeDataSource.Add("Resolved");
ResolvedColumn.DataSource = _outcomeDataSource;
ResolvedColumn.PropertyName = "Outcome";
}

Related

WinForms DateTimePicker to Selected Cell from DataGridView

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;
}

Get item by ID in a CollectionView

I'm writing a WPF application connected to a local Access database. In one of the application screens, one table data (named Service) is shown in individual textboxes, like a form, and the user can navigate through records, create new ones, delete, edit or search. Everything is done on the same table.
After a intensive research on how to navigate through records displayed in textboxes, I ended up using a DataSet and a CollectionView.
public partial class Entries : Window
{
AgendaDataSet agendaDataSet = new AgendaDataSet();
AgendaDataSetTableAdapters.ServiceTableAdapter serviceAdapter = new AgendaDataSetTableAdapters.ServiceTableAdapter();
CollectionView workSheetView;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.serviceAdapter.FillByDateAsc(agendaDataSet.Service);
this.DataContext = agendaDataSet.Service;
this.workSheetView = (CollectionView)CollectionViewSource.GetDefaultView(agendaDataSet.Service);
this.workSheetView.MoveCurrentToLast();
}
I got record navigation working using the CollectionView methods MoveCurrentToFirst(), MoveCurrentToNext(), etc. I also can create new records, edit and delete.
This is the method I use to create a new record:
private void btnNovo_Click(object sender, RoutedEventArgs e)
{
dynamic row = this.agendaDataSet.Service.NewMainRow();
this.agendaDataSet.Service.AddMainRow(row);
this.workSheetView.MoveCurrentToLast();
}
My problem is with record searching. I have a button that, when the user presses it, it asks for the PatientName he is searching. Then, the data about that Patient must appear on the various textboxes, ready to be consulted, edited or deleted.
Through the CollectionView, I only found the method GetItemAt() that gets a record based on it's row index. Since I am working with an Access database, I can't use the predicate ROW_NUMBER. And I don't think this approach would be the best.
So, how can I get an item based on it's ID, or PatientName, or any other field, and pass it as a row to the CollectionView?
Probably you don't need to get an item based on its ID or PatientName property.
Suppose that the user looks for "Andrew" as PatientName. Your code finds that the second row of your DataTable (called "Service") is the one the user is looking for.
You can use a simple static method to look for a DataRowView, something like this:
private static DataRowView FindDataRowView(DataView dataView, DataRow dataRow)
{
foreach (DataRowView dataRowView in dataView)
{
if (dataRowView.Row == dataRow)
{
return dataRowView;
}
}
return null;
}
and then you can select the object in your CollectionView:
collectionView.MoveCurrentTo(FindDataRowView(agendaDataSet.Service.DefaultView,
agendaDataSet.Service.Rows[2]));
Of course you can find the real DataRow index by using a foreach cycle or the Select method of DataTable.

Easycomplete Datagridviewcombobox column winforms

I am trying to create a datagridComboBoxcolumn in winforms with Suggestions Based on Loose Character Search similar to Easycomplete combobox. but I want this as Datagridview combobox.
I have created a grid with Datagridviewcombobox column and used autocomplete but it will search only from first characters. I want loose search. I used
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (e.Control is DataGridViewComboBoxEditingControl)
{
((ComboBox)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
((ComboBox)e.Control).AutoCompleteSource = AutoCompleteSource.ListItems;
((ComboBox)e.Control).AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
}
}
Please provide me a solution to create this type of datagridviewcombobox.
For that you have to create a custom DataGridiVew control. This is not a one line code or not a single class code. you have to make several classes for that.
public class MyDgv : DataGridView
{
....
}
And also create some classes like DataGridViewComboBoxColumn, DataGridViewComboBoxCell, DataGridViewEditingComboBoxControl
There is a tutorial how to create custom column in datagridview on msdn

How do I make a DataGridView immediately commit edits?

I have a master-detail layout with a section of popup menus (the Details) and a section with a DataGridView which holds the rows.
The popup-menu state is updated when the selected row in the DataGridView changes and the state in the DGV's selected row should update when the popup-menu changes.
All of this works except the row in the DataGridView doesn't immediately update when I change the value of the popup-menu. I have to select a different row in order to see my edits.
I'm assuming this is because the edit hasn't been committed until the selection changes.
My question is: How do I make the change to the popup become immediately reflected in the DataGridView?
I have experimented with calling EndEdit() in the SelectionChangeCommitted handler for the popup-menu, but this has no effect. I'm interested in a technique that would allow me to create a DataGridView that would behave as if there were no Undo mechanism to begin with. Ideally the solution would be generic and transplantable to other projects.
It looks like existing answers work well with BindingSource. In my case, where DataTable was directly used as a DataSource, they didn't work for some reason.
// Other answers didn't work in my setup...
private DataGridView dgv;
private Form1()
{
var table = new DataTable();
// ... fill the table ...
dgv.DataSource = table;
}
After some hair-pulling, I got it work without adding BindingSource indirection:
// Add this event handler to the DataGridView
private void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgv.BindingContext[dgv.DataSource].EndCurrentEdit();
}
private Form1()
{
dgv.CellEndEdit += dgv_CellEndEdit;
// ...
}
Here's what was going on. The answer was in the properties of the ComboBox instances. I needed to change their DataSourceUpdateMode from OnValidation to OnPropertyChanged. This makes sense. The DataGridView was very likely showing the current state of the data. It was just that the data hadn't been edited yet because focus had not left the ComboBox, validating the input.
Thanks to everyone for your responses.
this works well for me:
private void CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var dgw = (DataGridView) sender;
dgw.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Use this extension method. It works for all columns types, not just ComboBoxes:
public static void ChangeEditModeToOnPropertyChanged(this DataGridView gv)
{
gv.CurrentCellDirtyStateChanged += (sender, args) =>
{
gv.CommitEdit(DataGridViewDataErrorContexts.Commit);
if (gv.CurrentCell == null)
return;
if (gv.CurrentCell.EditType != typeof(DataGridViewTextBoxEditingControl))
return;
gv.BeginEdit(false);
var textBox = (TextBox)gv.EditingControl;
textBox.SelectionStart = textBox.Text.Length;
};
}
This method commits every change just after the change is made.
When we have a text column, after typing a character, its value will commit to the DataSource and the editmode of the cell will end.
Therefore current cell should return to edit mode, and position of the cursor set to end of text in order to enable user to continue typing reminder of the text.
Call the DataGridView.EndEdit method.
following will work
_dataGrid.EndEdit()
s fine once you set the value.

How do i save altered datagrid value into a datatable?

I have a dataset, and 2 datatables.
Datatable1 = Combobox source (This will display a list of options)
Datatable2 = DataGrid (This will display data relevant to the options in combo box)
Submit Button (populate datagrid based on combo box selected value)
When i select an item in combo box and click submit, it load up the relevant records in datagrid. If i then change a value in the datagrid and click the submit button, the value i have just changed, dissapears?
How can i make it so that any altered datagrid values amend the datable, so that even if i view different options, i can always return any, an retain any of the changed values?
Here is my code:
//Load the data grid according to the ComboCAtegory selection
public void Grid_Load()
{
DataSet();
var Result = from c in DataSet_Main.Tables[2].AsEnumerable()
where c.Field<string>("Test_Code").Equals(comboBox_CategorySelect.SelectedValue)
select c;
dataGridView_Main.DataSource = Result.AsDataView();
dataGridView_Main.Columns["Test_Code"].Visible = false;
dataGridView_Main.Columns["ID"].Visible = false;
dataGridView_Main.Columns["Description"].Visible = false;
dataGridView_Main.Columns["Expected_Result"].Visible = false;
}
private void buttonSubmit_Click(object sender, EventArgs e)
{
Grid_Load();
}
public void Fail()
{
DataTable dt = DataSet_Main.Tables[2];
//dataGridView_Main.SelectedRows[0].Cells["Check"].Value = "Fail";
dt.Rows[dataGridView_Main.SelectedRows[0].Index]["Check"] = "Fail";
}
private void buttonFail_Click(object sender, EventArgs e)
{
Fail();
}
Hope this makes sense?
I think your DataGrid is already bound to the Data Table. What you need to do is send the changes back to the data source so that they would be reflected in the second data table which is bound to the same data source. To do this, write an event handler for CellChanging event on the DataGrid and in that you can the call Update() method on your Data Adapter (if you are using one, that is) to send changes to the data source. Then, in the same event handler, update the items in the combo box by refreshing the data bind so that the combo box gets latest values from the second data table.
This way, whenever the cell changes its value in the DataGrid, you can check if it is the relevant cell which you want and update the combo box based on the changes in the data grid.
Apologies my bad..i am a boof head.
Tha datagrid IS bound automatically. Ive just realised i was calling my initial dataset() method - which is calling my database, in my datagrid_load method. thus everytime i was populating the datagrid, it was actually refreshing from the database not the datatable.
Thankyou your repy tho..

Categories

Resources