Binding a TextBox to a particular DataRowView in a BindingSource IList property - c#

In my application I have a table containing data for various regions within an organization with related tables containing statistical data about each region. For simplicity I'll represent them as:
____________ ________________ ________________
|Region | |RegionHasDemo | |DemoCategories|
| |1 *| |* 1| |
|-RegionID |----------|-RegionID |----------|-CatID |
|-City | |-CatID | |-SortOrder |
|-Zip | |-Population | |-Archive |
|-State | |______________| |______________|
|__________|
The DemoCategories table contains the types of demographic. For instance, lets say RegionHasDemo represented age populations for my regions. The CatIDs in DemoCategories would be values like Age20to30, Age20to40 ... etc. so that RegionHasDemo represents age group populations for all the regions.
In my application I want to create a form to add region data along with all of the related data. To do this I've created the two binding sources, one for RegionData and one for RegionHasDemo that contains RegionData as it's DataSource. For various reasons on my form I would like to enter the data for RegionHasDemo by binding individual text boxes to the DataRowViews contained in the List property of the RegionHasDemoBindingSource. Note, I do not want to use a grid view.
Here is the code that I use to bind to the text boxes every time the Current property of the RegionData changes:
Note: the table names are different from my sample, RegionData = UNITS; RegionHasDemo = UnitExp; DemoCategories = CatExp.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
namespace DBsample
{
public partial class Form1 : Form
{
private DataTable DataSource;
private string relatedTableName;
/// <summary>
/// Holsd the values needed to define my text box rows and
/// relate them to the table containing their human readable
/// names, sort order, and active record status.
/// </summary>
private struct textBoxRow
{
public string Key { get; set; }
public TextBox TBox { get; set; }
public Label NameLabel { get; set; }
public string Name { get; set; }
public int Value { get; set; }
}
textBoxRow[] rows;
public Form1()
{
InitializeComponent();
DataSource = injuryDB.UnitExp;
relatedTableName = "CatExpUnitExp";
}
private void Form1_Load(object sender, EventArgs e)
{
this.uNITSTableAdapter.Fill(this.injuryDB.UNITS);
this.catExpTableAdapter1.Fill(this.injuryDB.CatExp);
this.unitExpTableAdapter1.Fill(this.injuryDB.UnitExp);
// Fill data table
// Associate them in the struct in sorted order
// Get a list of categories
DataTable catTable = DataSource.ParentRelations[relatedTableName].ParentTable;
// Sort them while leaving out the ones we don't want
List<DataRow> tbr = (from r in catTable.AsEnumerable()
where r.Field<bool>("ActiveRecord")
orderby r.Field<int>("SortOrder")
select r).ToList();
// The rows we are going to show
rows = new textBoxRow[tbr.Count];
tableLayoutPanel1.RowStyles.Clear();
int rowIndex = 0;
// Create rows and add them to the form
foreach (DataRow r in tbr)
{
textBoxRow tRow = new textBoxRow();
Label lbl = new Label();
lbl.Text = r.Field<string>("CatName");
TextBox tb = new TextBox();
tRow.Key = r.Field<string>("Category");
tRow.Name = r.Field<string>("CatName");
tRow.TBox = tb;
tRow.NameLabel = lbl;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tableLayoutPanel1.Controls.Add(tRow.NameLabel, 0, rowIndex);
tableLayoutPanel1.Controls.Add(tRow.TBox, 1, rowIndex);
rows[rowIndex] = tRow;
rowIndex++;
}
// Refresh the bindings in the text boxes when the current item changes
unitCatExpBindingSource.CurrentItemChanged += currentItemChanged;
currentItemChanged(null, null);
}
private void uNITSBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
bool validated = this.Validate();
if(validated == true)
Debug.WriteLine("Validated data to be saved");
else
Debug.WriteLine("Did not validate data to be saved");
this.uNITSBindingSource.EndEdit();
int recordsUpdated = this.tableAdapterManager.UpdateAll(this.injuryDB);
Debug.WriteLine(string.Format("{0} records were changed", recordsUpdated));
}
private void currentItemChanged(object sender, EventArgs e)
{
if (rows == null) return;
// For some reason I have to pass this into the bindingSource's find method instead of a
// straight string of the column name. It has something to do with the fact that we are
// binding to a data relation instead of a DataTable i think. Wadded through so many forums for this...
PropertyDescriptor pdc = unitCatExpBindingSource.CurrencyManager.GetItemProperties()["Cat"];
// Rebind each text box row
foreach (textBoxRow tBoxRow in rows)
{
tBoxRow.TBox.DataBindings.Clear();
// If the record doesn't exist then that means the population for that group is zero
tBoxRow.TBox.Text = "0";
//tbr.TBox.Leave -= existingRecordTBoxChanged;
//tbr.TBox.Leave -= nonExistingRecordTBoxChanged;
// Get the index of the source I want to bind to using the text
int bindingIndex = unitCatExpBindingSource.Find(pdc, tBoxRow.Key);
//object bs = unitCatExpBindingSource[bindingIndex];
if (bindingIndex >= 0)
{
Binding b = tBoxRow.TBox.DataBindings.Add("Text", unitCatExpBindingSource[bindingIndex], "Jumps", true);
}
else
{
// TODO: Create an event that adds a new to the demo table if number not 0
}
}
}
// TODO: for this to work the delete options of the relationships
// for Units -> category tables need to be set to cascade
private void existingRecordTBoxChanged(object sender, EventArgs e)
{
TextBox tBox = (TextBox)sender;
if (tBox.Text == "" || tBox.Text == "0")
{
//DataRow d = (DataRow)tBox.DataBindings[0].DataSource;
}
}
private void nonExistingRecordTBoxChanged(object sender, EventArgs e)
{
TextBox tBox = (TextBox)sender;
if (!(tBox.Text == "" || tBox.Text == "0"))
{
// TODO: Add record to the database
}
}
}
<code>
My problem is that although when my binding seems to be working okay for viewing, i.e. the textboxes change when I navigate through the units. The changes don't save to the database. The changes I make in the text boxes will persist in the DataSet (when I move away from the record and come back they stay the same) but when I save the dataSet close the form and open it back up again the changes are gone. It's as if the dataSet wasn't notified that those values were changed. Furthermore, if I put the related data in a data grid along side my custom text box list I am able to modify the data from the DataGridView and I am able to save the data. Also, when I change the data in my TextBoxes the new values will show up in the DataGridView but still won't be saved...
I'm wondering is the method that I use to bind the text boxes to the data correct. Meaning, can I bind data in the form of a DataRowView contained inside a Binding source and expect it to behave the same as if I used the binding source directly?
Your help is much appreciated. I hope I've given enough information for you to go on. Keep in mind that this code sample is from a prototype. I understand that that it is not best practice. That said I would appreciate any constructive criticism.

After much trial and error I have found the problem. The issue is that I am binding my text box to a DataRowView object. I'm unsure but I think that a DataRowView is meant to be only specifically used within a GridView or ListView component. In my project I want to bind each DataRowView individually to separate text boxes which causes problems because the DataRowViews seem to share some kind of binding property that makes it so that only one of the text boxes gets bound properly. The solution is to pull the DataRow object from the DataRowView and bind the text boxes to that. If anyone has any input as to the differences between DataRows and DataRowViews in the context of data binding it would be much appreciated.

i have same probs in Combobox i have solved it. my code look like
<sdk:DataGridTemplateColumn Width="150" Header="EstimateIOName">
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="cbo" ItemsSource="{StaticResource IOList}" SelectedValue="{Binding Path=EstimateIOName,Mode=TwoWay}" SelectedValuePath="EstimateIOName" SelectionChanged="cbo_SelectionChanged" DropDownClosed="cbo_DropDownClosed" ></ComboBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Width="180" Header="Selected EstimatedIOName" IsReadOnly="True">
<sdk ataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBlock x:Name="cbo1" Text="{Binding Path=EstimateIOName,Mode=TwoWay}"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn>
My Code Behind Look like
LoadOperation<ATDueDate> Load1 = context1.Load<ATDueDate>(context1.GetATDueDatesByEntityQuery("I"));
Load1.Completed += (s, ea) =>
{
this.DataContext = Load1.Entities;
comboBox2.ItemsSource = Load1.Entities.Select(a => a.PackageName.Substring(1,2)).Distinct();
};
Button Click
private void button1_Click(object sender, RoutedEventArgs e)
{
context1.SubmitChanges();
MessageBox.Show("Changes Saved");
}

Related

How to bind Access database to DataTable?

I'm new to C# and Visual Studio.
I'm working on a project that is searching through a database of information.
I want to bind a database (Microsoft access file) to my datagridview
but I want it to work with my preexisting code which utilizes a datatable converted into a dataview.
My database has a lot of information and I don't want to put it in manually. I've tried binding the information directly to the datagridview (through datasource in the properties) but then searching doesn't work**. I've looked into sql but im trying to avoid learning 2 languages at the same time.
My projects basic functionality contains: 1 combobox (idCbo) containing the search query's 1 datagridview for displaying the information
this setup is for searching one column only, im going to duplicate the code for the oher columns
The name of the column in the datagridview selects the column(id) for filtering then the combo box(idCbo) searches that column for matching characters in the datagridview and comboBox list.
the combo box contains the values 1-100 for searching the column
public partial class Form1 : Form
{
DataTable dt = new DataTable();
DataView dataView;
public Form1()
{
InitializeComponent();
dt.Columns.Add("id", typeof(int));
for (int i = 0; i < 100; i++)
dt.Rows.Add(i);
dataView = new DataView(dt);
this.dataGridView1.DataSource = dataView;
}
private void idCbo_SelectedIndexChanged(object sender, EventArgs e)
{
string query = idCbo.Text;
dataView.RowFilter = $"convert(id,'System.String') LIKE '%{query}%'";
}
}
**
Binding the database to the datagridview while using this code renders column titles but not the information and the code cannot access the database, columns or the rows System.Data.EvaluateException: 'Cannot find column ...
Big thanks to Johng for assisting me with the code :)
CURRENT WORKING CODE
public Form1()
{
InitializeComponent();
}
public static BindingSource gridBindingSource;
private void idCbo_SelectedIndexChanged(object sender, EventArgs e)
{
string query = idCbo.Text;
gridBindingSource = (BindingSource)dataGridView1.DataSource;
if (gridBindingSource != null)
{
if (query == "All")
{
gridBindingSource.Filter = "";
}
else
{
gridBindingSource.Filter = "convert(id,'System.String') LIKE '%" + query + "%'";
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the '_InfoFinalv_2___CopyDataSet.Info' table. You can move, or remove it, as needed.
infoTableAdapter.Fill(this._InfoFinalv_2___CopyDataSet.Info);
idCbo.Items.Add("All");
for (int i = 1; i < 100; i++)
{
idCbo.Items.Add(i);
}
idCbo.SelectedIndex = -1;
}
private void idReset_Click(object sender, EventArgs e)
{
idCbo.SelectedIndex = -1;
}
If you have set up the grids data source in the designer “correctly” then using the DataView as you want can be simplified by using the existing BindingSource that is usually created when you set up the grid’s data source in the designer.
We can use the existing grid’s BindingSource and then use it’s Filter property as opposed to converting the BindingSource to a DataView to filter. This will allow us to set the filter in the grid WITHOUT having to “change” the grids data source.
Remove all the code you have in the form constructor obviously leaving the InitializeComponent(); and add the code below to the forms Load event. In the load event all we do is set up the combo box with the proper values. I added an “All” option to allow the user to “un-filter” the data in the grid.
private void Form1_Load(object sender, EventArgs e) {
// TODO: This line of code loads data into the 'database1DataSet.EmployeeDT' table. You can move, or remove it, as needed.
employeeDTTableAdapter.Fill(this.database1DataSet.EmployeeDT); // <- created by the designer
idCbo.Items.Add("All");
for (int i = 1; i < 100; i++) {
idCbo.Items.Add(i);
}
idCbo.SelectedIndex = 0;
}
Then in the combo boxes SelectedIndexChanged event... change the code as shown below. Cast the grids DataSource to a BindingSource and then use its Filter property.
private void idCbo_SelectedIndexChanged(object sender, EventArgs e) {
string query = idCbo.Text;
BindingSource GridBS = (BindingSource)dataGridView1.DataSource;
if (GridBS != null) {
if (query == "All") {
GridBS.Filter = "";
}
else {
GridBS.Filter = "EmpID LIKE '%" + query + "%'";
}
}
}
Here's the tip:
On the form load, make an ajax call to the database and fetch only the required data columns. Return data will be in JSON that can be used as data for DataTable.
I used it in an MVC project recently and it works fine. If you would like I can share the detailed code and logic.
Not sharing the code since I'm not sure if you are on .Net MVC.

Changing the back colour on a specific row of a Datagridview

I tried to change the back colour with a condition as follows. It doesn't work.
private void frmJobStat_Load(object sender, EventArgs e)
{
sql = "SELECT `jobno` AS JOB_NO,`lcode` AS LOCATION,iscompleted, iscannotrepair, isissued FROM `jobmaster`";
config.Load_DTG(sql, dgvJobNo);
dgvJobNo.Columns[1].Visible = false;
//dgvJobNo.Columns[2].Visible = false;
//dgvJobNo.Columns[3].Visible = false;
//dgvJobNo.Columns[4].Visible = false;
dgvJobNo.Columns[0].Width = 100;
dgvJobNo.DefaultCellStyle.Format = "000000";
for (int i = 0; i < dgvJobNo.Rows.Count - 1; i++)
{
if (Boolean.Parse(dgvJobNo.Rows[i].Cells[2].Value.ToString()))
{
dgvJobNo.Rows[i].DefaultCellStyle.BackColor = Color.Blue;
}
}
//foreach (DataGridViewRow rw in dgvJobNo.Rows)
//{
// if (rw.Cells[2].Value.ToString()=="1")
// {
// dgvJobNo.DefaultCellStyle.BackColor = Color.Red;
// }
//}
}
I have load one column from a table with true/false values. I need to change the colour when it is true.
Can anyone help me?
People who use a DataGridView quite often tend to directly fiddle with Cells and how values are displayed. It is way easier to use DataBinding: separate the data from the way that it is displayed using DataGridView.DataSource.
Use the DataSource
I haven't got the faintest Idea what you are showing in your DataGridView, so let's assume you want to show Products.
So somewhere you have a class to save and retrieve Products from some place. Of course you hide that the data is fetched from a database, and how this is fetched (DbConnection and DbCommand? Or maybe entity framework and LINQ?). All you know is that you can store Products and retrieve them later on:
interface IRepository
{
int AddProduct(Product product) // returns Id
Product FetchProduct(int id);
IEnumerable<Product> FetchProducts(...); // fetches several Products to display
...
Oh, before I forget:
class Product
{
public int Id {get; set;}
public string Name {get; set;}
public decimal Price {get; set;}
public int Stock {get; set;}
...
}
Suppose you have a form with a DataGridView where you want to show some Products. Using visual studio designer you have added a DataGridView and some columns
In the constructor:
public MyForm : ...
{
InitializeComponent();
this.columnId.DataPropertyName = nameof(Product.Id);
this.columnName.DataPropertyName = nameof(Product.Name);
this.columnPrice.DataPropertyName = nameof(Product.Price);
...
}
Of course you have access to the repository and a method that fetches the Products that you want to show:
private IRepository ProductRepository => ...
private IEnumerable<Product> FetchProductsToDisplay()
{
return this.ProductRepository.FetchProducts(...);
}
Now to display the fetched Products, we use the DataSource of the DataGridView in a BindingList:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
private void ShowInitialProducts()
{
this.DisplayedProducts = new BindingList<Product>( this.FetchProductsToDisplay().ToList());
And of course, while Loading the form:
private void OnFormLoading(object sender, ...)
{
this.ShowInitialProducts();
}
This is enough to show all Products. If the operator Adds or Removes some rows, or edits the values of the rows, they are automatically updated. If the operator indicates that he has finished editing the Products, for instance by clicking OK button:
private void OnButtonOkClicked(object sender, ...)
{
this.ProcessEditedProducts();
}
private void ProcessEditedProducts()
{
ICollection<Product> displayedProducts = this.DisplayedProducts();
// find out which Products are added / removed / changed
this.ProcessProducts(displayedProducts);
}
By the way, did you notice that my procedures are always very small: they do only one thing, they serve one purpose. This makes them easy to understand, good to reuse, easy to test and to change.
Intermezzo: some useful functions
The following small methods might be handy. If you have a DataGridViewRow, you can get DataGridViewRow.DataBoundItem to get the Product that is shown in the Row.
// Get the Product that is displayed in row with rowIndex
private Product DisplayedProduct(int rowIndex)
{
return (Product)this.dataGridView1.Rows[rowIndex].DataBoundItem;
}
// Get the Currently Selected Product
private Product CurrentProduct()
{
return (Product)this.dataGridView1.CurrentRow.DataBoundItem;
}
// Get all Selected Products (if you allow multi-selecting
private IEnumerable<Product> GetSelectedProducts()
{
return this.dataGridView1.SelectedRows.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
}
Back to your question
Apparently, there is a predicate which makes that you want to give some Cells a different background Color. For example: if the stock is zero, you want it red, and if the stock is low, you want it orange. Otherwise you want a white background
Color ZeroStockColor = Color.Red;
Color LowStockColor = Color.Orange;
int LowStockValue = 10; // 10 or less: Stock is low
Just before the value of a cell is displayed, you get the chance to format the cell, using event DataGridView.CellFormatting.
If the column that shows the Stock is being formatted, we want to check the Value of the Stock. If it is zero, we want red background, if it is low, we have an orange background.
private void OnCellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// e contains the rowIndex and columnIndex
if (e.ColumnIndex = this.columnStockValue.Index)
{
// Formatting the cell that contains the stock value
Product productToFormat = this.DisplayedProduct(e.RowIndex);
DataGridViewCell cellToFormat = this.dataGridView1
.Rows[e.RowIndex].Cells[e.ColumnIndex];
if (productToFormat.Stock == 0)
{
// no stock: RED!
cellToFormat.Style.BackColor = ZeroStockColor;
}
else if (productToFormat.Stock <= LowStockValue)
{
// Low stock: Orange!
cellToFormat.Style.BackColor = LowStockColor;
}
else
{
// enough stock: use default cell Style of this column
cellToFormat.Style = null;
}
}
}
If a DataGridViewCell.Style is not null, then this Style is used to format the cell. if the Style is null, then DataGridViewColumn.DefaultCellStyle is used, unless that value is also null. In that case we go even higher: DataGridView.DefaultCellStyle
Conclusion
If you use DataBinding, it is easy to show your data in the DataGridView and to access the objects in the DataGridView: the current one, the selected ones or the one that is displayed in the 10th row.
To format a cell, use DataGridViewCell.Style, or the DefaultCellStyle in the column, or in the DataGridView
Use event DataGridView.CellFormatting to format the cell just before it will be displayed.
It is not working because in C#, if you create a new object just by assignment, it will not generate child references so
dgvJobNo.Rows[i].DefaultCellStyle.BackColor
could be non existent here.
You have to do something like this
for (int i = 0; i < dgvJobNo.Rows.Count - 1; i++)
{
if (Boolean.Parse(dgvJobNo.Rows[i].Cells[2].Value.ToString()))
{
var row= new Rows();
var a = new DefaultCellStyle();
a.BackColor= Color.Blue;
row.DefaultCellStyle = a;
dgvJobNo.Rows[i] = row;
}
}

How to dynamically filter DataGridView using TextBox data?

I created a simple DataGridViewa with a single column and added a TextBox above.
Currently the text actually a DataTable (I though this would make things easier for filtering) with 2 columns, number and text (I hide the number in the DataGridView ). I can change it to any other class if required.
When the user enters a letter in the TextBox, I want to dynamically filter and show only the lines containing this text.
I load the data to the DataGridView like this:
private void PhrasesForm_Load(object sender, EventArgs e)
{
phrasesDataGridView.ReadOnly = true;
phrasesDataGridView.DataSource = _phrases.Phrases;
phrasesDataGridView.Columns[0].Visible = false;
this.phrasesDataGridView.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
When add another letter the filter will be readjusted.
What do I write here...
private void filterBox_TextChanged(object sender, EventArgs e)
{
}
If you have a DataGridView, or any Control with a DataSource, consider using Nuget Package BindingListView. It has functionality to sort the binding list by any column on a mouse click, but it also has functionality to filter the data.
List<Customer> customers = ...
BindingListView<Customer> customerView = new BindingListView<Customer>(customers);
dataGridView1.DataSource = customerView;
And presto, if shows all Customers. The columns depend on you column definition.
To filter the customers, for instance, show only customers with born before a certain date:
void ShowCustomers(DateTime limitDate)
{
customerView.ApplyFilter( customer => customer.BirthDay < limitDate));
}
Result: only the older Customers are shown.
You can do it similarly:
void ShowItemsWithPhraseStart(string phraseStart)
{
myView.ApplyFilter(row => row.Phrase.StartsWith(phraseStart));
}
And bingo: the datagrid shows only the row with a value for property Phrase that starts with phraseStart.
private void OnTextBocChanged(object sender, ...)
{
var text = this.TextBox1.Text;
ShowItemsWithPhraseStart(text);
}

Error after setting different datasources for datagridview combobox cells

I am trying to display in DataGridView control names and fields of database tables. In every row name of a table is displayed in the first cell, and second column allows to choose any of the fields of that table.
To do that, I am handling 'DataGridView.CellBeginEdit' event and fill DataSource for cell with names of the fields. When I am trying to edit those cells, provided list is displayed correctly and can b chose just fine.
However when I try to do the same in another row, I start getting DataError events about the cell I have edited.
Event arguments for DataRow have 'Formatting|Display' in Context field, and have the message "Value not allowed in DataGridViewComboBoxCell" (or close to it). In debug, the cell event references has correct Value field, but its DataSource is null and its FormattedValue is empty string. And previously displayed text changes to blank.
How should this be resolved correctly? Should I derive my own custom datagridview cell that displays text but has a combobox editor? If so, how?
Edit: here is code I am currently using:
public class FieldDataNeededEventArgs: EventArgs
{
public List<string> FieldNames
{
get; private set;
}
public string TableName
{
get; private set;
}
public ReferenceFieldDataNeededEventArgs(stringdata)
: base()
{
FieldNames = new List<string>();
TableName= data;
}
}
...
public event EventHandler<FieldDataNeededEventArgs> FieldDataNeeded =
new EventHandler<FieldDataNeededEventArgs>((sender, args) => { });
...
//handler for CellBeginEdit
//dgvMatch - dataGridView, DocsReferenceToTableMatch is class that datagridview is bound to
private void dgvMatch_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
if (e.ColumnIndex == dgvMatch.Columns["TableKey"].Index)
{
DocsReferenceToTableMatch data = dgvMatch.Rows[e.RowIndex].DataBoundItem as DocsReferenceToTableMatch;
FieldDataNeededEventArgs ea = new FieldDataNeededEventArgs(data.TableName);
FieldDataNeeded(this, ea);
var cell = (dgvMatch.Rows[e.RowIndex].Cells[e.ColumnIndex] as DataGridViewComboBoxCell);
cell.DataSource = ea.FieldNames;
}
}
//example handler for the FieldDataNeeded event
static void ld_ReferenceFieldDataNeeded(object sender, ReferenceFieldDataNeededEventArgs e)
{
for (int i = 0; i < 4; i++)
{
e.FieldNames.Add(String.Format("{0}_fld{1}", e.ReferenceName, i));
}
}

DataGridViewComboBoxColumn name/value how?

I thought this was simple like in Access.
User needs to set the value of one column in a datatable to either 1 or 2.
I wanted to present a combobox showing "ONE", "TWO" and setting 1 or 2 behind the scene, like I did lots of times in Access-Forms.
On the other side, if the table is shown it shall not show 1 or 2 but the corresponding string in the ComboBox.
How can I get this simple task to work??
I assume you meant DataGridView, which is for Windows Forms, while the GridView is for ASP.NET although you tagged your question as such.
How are you binding the data to the DataGridViewComboBoxColumn? You'll need to set the DisplayMember and ValueMember properties on the DataGridViewComboBoxColumn while setting its DataSource. The MSDN link to DisplayMember shows an example, but it doesn't quite show what you're requesting since it sets both properties to the same thing.
The DisplayMember would be the text you want the user to see, and the ValueMember would be the hidden underlying value associated with it.
For the sake of an example, let's say you have a Choice class in your project that represents your selections and looks like this:
public class Choice
{
public string Name { get; private set; }
public int Value { get; private set; }
public Choice(string name, int value)
{
Name = name;
Value = value;
}
private static readonly List<Choice> possibleChoices = new List<Choice>
{
{ new Choice("One", 1) },
{ new Choice("Two", 2) }
};
public static List<Choice> GetChoices()
{
return possibleChoices;
}
}
GetChoices() will return a list containing your choices. Ideally you would have such a method in a service layer, or you could build your own list elsewhere if you wanted to (in your form's code behind). For simplicity I've lumped it all together in the same class.
In your form you would bind the list to the DataGridViewComboBoxColumn as follows:
// reference the combobox column
DataGridViewComboBoxColumn cboBoxColumn = (DataGridViewComboBoxColumn)dataGridView1.Columns[0];
cboBoxColumn.DataSource = Choice.GetChoices();
cboBoxColumn.DisplayMember = "Name"; // the Name property in Choice class
cboBoxColumn.ValueMember = "Value"; // ditto for the Value property
You should now see "One" and "Two" in the combobox. When you get the selected value from it, it should be the underlying 1 or 2 value.
That's the idea behind using DisplayMember/ValueMember. This should get you going and help you adapt the datasource you were using.
This is how you read the value from the grid when the value in the combobox changes:
dataGridView1.EditingControlShowing += dataGridView1_EditingControlShowing;
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dataGridView1.CurrentCell.ColumnIndex == 0 && e.Control is ComboBox)
{
ComboBox comboBox = e.Control as ComboBox;
comboBox.SelectedIndexChanged += LastColumnComboSelectionChanged;
}
}
private void LastColumnComboSelectionChanged(object sender, EventArgs e)
{
var sendingCB = sender as DataGridViewComboBoxEditingControl;
object value = sendingCB.SelectedValue;
if (value != null)
{
int intValue = (int)sendingCB.SelectedValue;
//do something with value
}
}
sources: this post
Note that in the example given above, DataGridViewComboBoxColumn cboBoxColumn = (DataGridViewComboBoxColumn)dataGridView1.Columns[0]; does not work - the compiler won't allow you to cast a dateGridView column to a DataGridViewComboBoxColumn. I'm interested in the example because it's one of the few that I've seen that includes a local data source instead of a database. My application reads data from a file. Hopefully someone sees this and updates the example to work in a current version of .NET.

Categories