I have a datagridview with quantity and price and net price. By default quantity is 1 and the relevant price is there. I want to edit the quantity so net price will come according to that. For e.g if I edit quantity to 2 the netprice will come for 2. it will take price from price column and calculate
Here's my code.
private void grvItems_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
foreach (DataGridViewRow row in grvItems.Rows)
{
if (e.ColumnIndex == grvItems.Columns["Quantity"].Index)
{
grvItems.EndEdit();
decimal quantity = Convert.ToDecimal(row.Cells["Quantity"].Value);
decimal price = Convert.ToDecimal(row.Cells["Amt"].Value);
decimal netprice = (quantity * price);
row.Cells["netprice"].Value = Math.Round((netprice), 2);
}
}
CalculateTotal();
}
But the evnt is not firing. If i edit quantity, the net price is not relflected. Please suggest ideas.
Your change will reflect only after the cell will lost focus.
If you want to see the change during the edit, you can use the other event:
dataGridView1_CellBeginEdit
You can get help from MSDN, there is a good and simple example there: CellBeginEdit and CellEndEdit
UPDATE
As already mentioned, let's ensure you have registered your events.
In your form constructor, write as following:
public Form1() // Constructor
{
InitializeComponent();
grvItems.CellBeginEdit += grvItems_CellBeginEdit;
grvItems.CellEndEdit += grvItems_CellEndEdit;
}
UPDATE
It is hard to help you in that way. I have created a similar example. Open a new Windows Form application, paste the following code.
You need to add a DataGridView on the form (Form1).
This example is working fine. After doing it, try to see what are the differences with your code.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
System.Data.DataTable dt = new System.Data.DataTable();
dt.Columns.Add("Quantity", Type.GetType("System.Decimal")); // You can change it to Int32
dt.Columns.Add("Amt", Type.GetType("System.Decimal"));
dt.Columns.Add("netprice", Type.GetType("System.Decimal"));
var row = dt.NewRow();
row["Quantity"] = 1;
row["Amt"] = 2.5;
row["netprice"] = 0;
dt.Rows.Add(row);
dataGridView1.DataSource = dt;
}
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (e.ColumnIndex == dataGridView1.Columns["Quantity"].Index)
{
dataGridView1.EndEdit();
decimal quantity = Convert.ToDecimal(row.Cells["Quantity"].Value);
decimal price = Convert.ToDecimal(row.Cells["Amt"].Value);
decimal netprice = (quantity * price);
row.Cells["netprice"].Value = Math.Round((netprice), 2);
}
}
// My suggestion:
// No need to go through all the rows, just the current one
/*
if (e.ColumnIndex == dataGridView1.Columns["Quantity"].Index)
{
// dataGridView1.EndEdit(); // No need this line, you are already in CellEndEdit event
var currentRow = dataGridView1.Rows[e.RowIndex];
decimal quantity = Convert.ToDecimal(currentRow.Cells["Quantity"].Value);
decimal price = Convert.ToDecimal(currentRow.Cells["Amt"].Value);
decimal netprice = (quantity * price);
currentRow.Cells["netprice"].Value = Math.Round((netprice), 2);
}
*/
}
}
You might want to try using the CellValueChanged event as an alternative to CellEndEdit and see if it does what you want.
Also, consider having the DataGridView do most of the work:
public partial class AlternativeToCellEndEdit : Form
{
public AlternativeToCellEndEdit()
{
InitializeComponent();
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
}
// Try handling CellValueChanged instead of CellEndEdit
private void DataGridView1_CellValueChanged(Object sender, DataGridViewCellEventArgs e)
{
// Our MyRecord class is smart and makes the calculation
// any time we change Quantity or Amt. Here, we only need
// to refresh the DataGridView to show the updated info.
dataGridView1.Refresh();
}
// Make a class to represent a line item in the DataGridView.
// When the Quantity or Amt changes, it recalculates itself.
class MyRecord
{
public string Description { get; set; } = "New Item";
int mQuantity = 1;
public int Quantity
{
get { return mQuantity; }
set { mQuantity = value; NetPrice = Quantity * Amt; } // Recalc
}
double mAmt = 0.00;
public double Amt
{
get { return mAmt; }
set { mAmt = value; NetPrice = Quantity * Amt; } // Recalc
}
public double NetPrice { get; private set; } // Makes this cell Read-Only in the DataGridView
}
// Tell the DataGridView that we want to display our custom class.
BindingList<MyRecord> Items = new BindingList<MyRecord>();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// Bind data to the view.
// Now DataGridView does all the work for us.
dataGridView1.DataSource = Items;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Make everything look nice in the way of formatting.
DataGridViewColumn col;
col = dataGridView1.Columns["Description"];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col = dataGridView1.Columns["Quantity"];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
col = dataGridView1.Columns["Amt"];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
col.DefaultCellStyle.Format = "F2";
col = dataGridView1.Columns["NetPrice"];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
col.DefaultCellStyle.Format = "F2";
// Add the first default item
Items.Add(new MyRecord());
}
}
Related
I have a problem in multiplying tow cell of one row and sum one of the column after multiplying in datagridview and show the final result in textbox in C# , please guide me how can I do this enter image description here
private void btnsum_Click(object sender, EventArgs e)
{
int multiplication = 0;
int sum = 0;
for (int i = 0; i < saleDataGridView.Rows.Count; i++)
{
if (saleDataGridView != null)
{
if (int.TryParse(saleDataGridView.Rows[i].Cells[1].Value.ToString(), out sum) && int.TryParse(saleDataGridView.Rows[i].Cells[6].Value.ToString(), out multiplication))
{
int total = sum * multiplication + Convert.ToInt32(saleDataGridView.Rows[i].Cells[1].Value);
// sum += *Convert.ToInt32(saleDataGridView.Rows[i].Cells[6].Value);
txttotal.Text = total.ToString();
}
else {
MessageBox.Show("error");
}
enter image description here
I think it is easy by using Binding DataGridView using generic List.
For example
class MyModel
{
public double Price { get; set; }
public double Quantity { get; set; }
}
Binding this model to DataGridView and calculating totals using LINQ, after click button example:
public partial class Form1 : Form
{
private List<MyModel> myData;
public Form1()
{
InitializeComponent();
myData = new List<MyModel>() { new MyModel() {Price=35000,Quantity=100 }, new MyModel() { Price = 150000, Quantity = 322 } };
dataGridView1.DataSource = myData;
}
private void btnCalc_Click(object sender, System.EventArgs e)
{
var result = myData.Sum(s => s.Price * s.Quantity);
txtBoxTotal.Text = result.ToString();
}
}
Result:
other solution, if you want calculate by using DataGridView Cells:
double result = 0;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
var price = double.Parse(row.Cells[0].Value.ToString());
var quantity = double.Parse(row.Cells[1].Value.ToString());
result += price * quantity;
}
txtBoxTotal.Text = result.ToString();
For your case:
private void btnsum_Click(object sender, EventArgs e)
{
var result = 0;
foreach (DataGridViewRow row in saleDataGridView.Rows)
{
int price, quantity;
if (int.TryParse(row.Cells[1].Value.ToString(), out price) && int.TryParse(row.Cells[6].Value.ToString(), out quantity))
result += price * quantity;
else
MessageBox.Show("error");
}
txttotal.Text = result.ToString();
}
There are numerous ways you can approach this. Obviously you can loop through the grid rows and calculate the total for each row and sum all those values. Or if the grid has a data source, you could loop through those rows/items and get the totals also.
Therefore, since you do not say if the grid has a data source or not, this makes things difficult to suggest a proper solution. Example, if the grid’s underlying data source is a DataTable, then, you could “add” a “Total” “Expression” column to the grid that would calculate the “Price” * “Quantity” value for each row. This would remove one calculation from your code. You could also set the text boxes text to the SUM of all “Total” cells in the DataTable with the DataTable’s Compute function, thus eliminating another calculation you have to code. Any time the grids “Price” or “Quantity” value changes, then, we would update the text box sum total using the data tables Compute function.
Or as Mansur’s answer suggest, you could create a simple class to manage both the grid’s data source in addition to the text boxes data binding. This is the approach used below. In the Item class there would be three properties where “Price” and “Quantity” are both “editable” properties. The third property will be a read only “Total” that will return the “Price” * “Quantity” value.
We could simply make a list of Item objects and it should make things easier, however, I say let’s go one step further and create another Class called ListOfItems it will have two properties. A BindingList of Item objects AND a decimal read only property that returns the SumTotal of ALL the items in the list.
The idea with this class is that we can now bind BOTH the grid AND the TextBox to the “same” DataSource. The grid’s DataMember would be the BindingList of Item objects, and to bind the TextBox we will set its DataMember to the SumTotal property. This should make things much easier as far as calculating each rows total and the total of all rows.
public class Item {
public decimal Price { get; set; }
public int Quantity { get; set; }
public decimal Total => Price * Quantity;
}
public class ListOfItems {
public BindingList<Item> Items { get; set; }
public ListOfItems() {
Items = new BindingList<Item>();
}
public decimal SumTotal => Items.Sum(x => x.Total);
}
However, there is one small issue. If we look at the ListOfItems Class… we can see that SumTotal will reflect the current state of Items List, however if the list changes, then we will still need some mechanism to signal to the text box to “update” the total. In addition, when the grid cell values are changed by the user, there are certain situations where the data in the grid, may not necessarily exist in the underlying data source. In that case the total could be wrong.
These issues (among others) could be resolved if we simply use a BindingSource. If we use a BindingSource and set it’s DataSource to our ListOfItems object, then all we need to do when a cells price or quantity changes is to call the BindingSource’s ResetBinding method and it should update the grid and text box in one step. Even if this was not needed, using a BindingSource in a grid has numerous advantages… like this.
To give this a test, you can create a new winforms solution, and drop a DataGridView along with a TextBox for the sum total onto the form. There are two global variables… the BindingSource and the ListOfItems object. The code below is to generate some test data.
private ListOfItems GetData() {
ListOfItems listOfItems = new ListOfItems();
listOfItems.Items.Add(new Item { Price = 10.50m, Quantity = 2 });
listOfItems.Items.Add(new Item { Price = 1.0m, Quantity = 3 });
listOfItems.Items.Add(new Item { Price = 1.50m, Quantity = 3 });
return listOfItems;
}
The load method to set up everything…
BindingSource bs;
ListOfItems AllItems;
public Form1() {
InitializeComponent();
dataGridView1.CellValueChanged += new DataGridViewCellEventHandler(dataGridView1_CellValueChanged);
}
private void Form1_Load(object sender, EventArgs e) {
AllItems = GetData();
bs = new BindingSource();
bs.DataSource = AllItems;
dataGridView1.DataSource = bs;
dataGridView1.DataMember = "Items";
textBox1.DataBindings.Add("Text", bs, "SumTotal");
}
Finally, the grids CellValueChanged event to update the text box when a “Price” or “Quantity” cell changes. All we need to do is Reset the binding source for the text box to update.
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (dataGridView1.Columns[e.ColumnIndex].Name == "Price" ||
dataGridView1.Columns[e.ColumnIndex].Name == "Quantity") {
if (dataGridView1.Rows[e.RowIndex].Cells["Price"].Value != null &&
dataGridView1.CurrentRow.Cells["Quantity"].Value != null) {
if (!string.IsNullOrWhiteSpace(dataGridView1.CurrentRow.Cells["Price"].FormattedValue.ToString()) &&
!string.IsNullOrWhiteSpace(dataGridView1.CurrentRow.Cells["Quantity"].FormattedValue.ToString())) {
bs.ResetBindings(false);
}
}
}
}
Lastly, as suggested earlier, you could do the same thing using a DataTable, by changing the Items property of ListOfItems class to a DataTable and either use LINQ or the DataTable’s Compute function to compute the sum total. In my tests, this will require and extra step, however it is trivial.
I hope this makes sense.
private void button1_Click(object sender, EventArgs e) {
int sum = 0;
for (int i = 0; i < dataGridView1.Rows.Count; i++) {
sum += Convert.ToInt32(dataGridView1.Rows[i].Cells[4].Value);
}
txtTotal.Text = sum.ToString();
}
I have a C# WPF app, and my goal is to get the sum of selected row values from a DataGrid, and set a textbox with this total. This sum calculation is triggered by an event that detects when the selected items have changed.
The problem is that when I select many rows at once or select all with Ctrl + A, I get unpredictable sum values.
To troubleshoot, I bound the datagrid with 100 rows, each with an amount of 1. I then selected all items with Ctrl + A. The total sum should be 100, but it caps at 7 or 8 units.
Below is what I have so far. Anyone see the problem?
private void DgDailyTransactions_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
decimal sum = 0;
for (int i = 0; i < dgDailyTransactions.SelectedItems.Count; i++)
{
TextBlock tb = dgDailyTransactions.Columns[1].GetCellContent(dgDailyTransactions.SelectedItems[i]) as TextBlock;
if (tb != null)
{
sum += Convert.ToDecimal(tb.Text);
}
}
tbxSelectedDailyTransactionsTotal.Text = sum.ToString();
}
Suppose your class looks like this:
public class Sales
{
public int Order { get; set; }
public decimal Amount { get; set; }
}
Make a simple method as under:
private void FindAmount()
{
decimal totalSum = 0;
//Add amounts of selected
if (MyDTGRID.SelectedItems.Count > 0)
{
for (int i = 0; i <= dgDailyTransactions.SelectedItems.Count - 1; i++)
{
Sales sales = dgDailyTransactions.SelectedItems[i] as Sales;
decimal amount = sales.Amount;
totalSum += amount;
}
}
myTextBlock.Text = totalSum.ToString();
}
Simply call the method in your selectedCellsChanged Event
private void DgDailyTransactions_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
FindAmount();
}
I hope this helps.
for the sum method you can do it using linq (if you don't know I strongly advise you to learn linq, very strong tool in C#), also would advise you to use MVVM, will make your code much easier to debug in future :
private void dgDailyTransactions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
List<Sales> myList=dgDailyTransactions.SelectedItems.Cast<Sales>();
decimal totalSum = 0;
if (myList.Count() > 0)
{
totalSum = myList.Sum(item => item.Amount);
}
myTextBlock.Text = totalSum.ToString();
}
in MVVM (if you use it) you would need just that line in Model :
private void dgDailyTransactions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
contexte.SelectedSales= new ObservableCollection<Sales>(dgDailyTransactions.SelectedItems.Cast<Affaire>());
}
then you use formula in your ViewModel in set section of SelectedSales
I have two datagridviews. datagridview1 and datagridview2. When I add a product from datagridview1 to datagridview2 the quantity of the product in datagridview1 is transferred to datagridview2. Now when I remove a product from datagridview2 I need it to transfer the quantity back to datagridview1.
Here is my code.:
private void btnRemove_Click(object sender, EventArgs e)
{
int ind = findIndexForItem(dgvPOScart.CurrentRow.Cells[0].Value.ToString());
int dgvCARTquantity = Convert.ToInt32(dgvPOScart.CurrentRow.Cells[4].Value.ToString());
int dgvPOSquantity = Convert.ToInt32(dgvPOScart.Rows[ind].Cells[5].Value.ToString());
int dgvnewADDquantity;
dgvnewADDquantity = dgvPOSquantity + dgvCARTquantity;
foreach (DataGridViewRow item in this.dgvPOScart.SelectedRows)
{
dgvPOScart.Rows.RemoveAt(item.Index);
}
}
And the code for the helper:
private int findIndexForItem(string name)
{
int ind = -1;
for (int i = 0; i < dgvPOSproduct.Rows.Count; i++)
{
if (dgvPOSproduct.Rows[i].Equals(name))
{
ind = i;
break;
}
}
return ind;
}
How can I properly call ind? Rows[ind] is wrong because ind is the product ID or value or cell[0] and not row index. Or is there an easier way to do this?
Your code is a little weird, you are foreach-ing the SelectedRows but you're only calculating the new quantity for the current row, why?
Also, you shouldn't look at the products by their name since you have their ID (which is more unique than a name).
In order for this to work, you'll need something like this:
private void btnRemove_Click(object sender, EventArgs e)
{
foreach (var row in dgvPOScart.SelectedRows)
{
// Get the row in dgvProducts and the quantity he'll gain back
var productRow = dgvPOSproduct.Rows[FindRowIndexByID(row.Cells[0].Value)];
int qtyToAdd = Convert.ToInt32(row.Cells[4].Value);
// Add the quantity back
productRow.Cells[5].Value = Convert.ToInt32(productRow.Cells[5].Value) + qtyToAdd;
}
}
private int FindRowIndexByID(string id)
{
for (int i = 0; i < dgvPOSproduct.Rows.Count; i++)
{
if (dgvPOSproduct.Rows[i].Cells[0].Value == id)
{
return i;
}
}
return -1;
}
I just want to ask how I can update or change the cell value of column 'quantity' in my DataGridView when I select a certain row.
I have 3 columns in my DataGridView which are Item Name, Quantity and Price.
Every time I click the button 'add' or 'less' on my form, the quantity of the selected row item will either add to or subtract 1 from the quantity column and the price will also update every time the quantity increments or decrements. Here is my code when I add my item to my DataGridView.
I'm not really sure if I got it right to add items to my datagrid.
cmd = new MySqlCommand("SELECT * FROM tblmenu", dbConn);
MySqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
Button btn = new Button();
btn.Text = rdr["menuName"].ToString();
btn.Width = 126;
btn.Height = 80;
btn.Click += delegate
{
MySqlConnection cnn2 = new MySqlConnection(sqlConn.connString);
cnn2.Open();
cmd = new MySqlCommand("SELECT menuName, menuPrice FROM tblmenu WHERE menuName = #name", cnn2);
cmd.Parameters.AddWithValue("#name", btn.Text);
MySqlDataReader rdr2 = cmd.ExecuteReader();
while (rdr2.Read())
{
dataGridView1.Rows.Add(rdr2.GetString("menuName").ToUpper(), 1, rdr2.GetDouble("menuPrice"));
}
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
lblQty.Text = dataGridView1.RowCount.ToString();
};
}
I have tried this but when I click another set of item, the quantity from the previous selected item still increments.
private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
Add.Click += delegate
{
dataGridView1.Rows[e.RowIndex].Cells["quantity"].Value = Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells["quantity"].Value) + 1;
};
Minus.Click += delegate
{
dataGridView1.Rows[e.RowIndex].Cells["quantity"].Value = Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells["quantity"].Value) - 1;
};
}
I suggest you consider binding your DataGridView to a data source. You should not perform data operations on the UI if you can avoid it.
To demonstrate this,
private void quantityChangeClick(object sender, EventArgs e)
{
addToQuantity((Button)sender == this.Add ? 1 : -1);
updateTotal();
}
private void addToQuantity(int howMuch)
{
var selectedRowIndices = dataGridView1.SelectedRows.OfType<DataGridViewRow>().Select(ro => ro.Index);
this.rows.Where((r, i) => selectedRowIndices.Contains(i)).ToList().ForEach(
r => r.Quantity = Math.Max(0, r.Quantity + howMuch));
this.dataGridView1.Refresh();
}
// calculate the total from the data source as well
private void updateTotal()
{
var total = Math.Round(this.rows.Sum(r => r.Quantity * r.Price), 2, MidpointRounding.AwayFromZero);
this.TotalLabel.Text = string.Format("₱{0:0.00}", total);
}
I have used some dummy data to demo this on my end, that is the Row class. Actually, you could do a similar thing, and add a Row to the data source rows with each record from your database.
private class Row
{
public string ItemName { get; set; }
public int Quantity { get; set; }
public double Price { get; set; }
public Row(string i, int q, double p)
{
this.ItemName = i;
this.Quantity = q;
this.Price = p;
}
}
private List<Row> rows = new List<Row>();
private void Form1_Load(object sender, EventArgs e)
{
// instead, here you will add your rows from SQL
rows.AddRange(new Row[]
{
new Row("item1", 0, 500),
new Row("item2", 0, 400),
new Row("item3", 0, 850)
});
this.dataGridView1.DataSource = rows;
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
Add.Click += quantityChangeClick;
Minus.Click += quantityChangeClick;
}
First of all, using dataGridView1_CellContentClick does not serve the purpose at all. You intend to increase or decrease quantity on Add or Minus button click. I would use dataGridView1.CurrentRow property to get hold of CurrentRow and manipulate quantity column.For updating price column, you should have a Rate column or a constant Rate value to multiply it with quantity. Do you have it? If not then I don't see how you arrive on price calculation. However assuming you have one :
void quantity_change(object sender, EventArgs e){
var row=dataGridView1.CurrentRow;
if(row==null || row.Index<0)
return;
var unit = (sender==add)?1:-1;
var quantity = Convert.ToInt32(row.Cells["quantity"].Value) + unit;
row.Cells["quantity"].Value = quantity;
//assuming you have rate column...
var rate = Convert.ToDouble(row.Cells["Rate"].Value);
row.Cells["Price"].Value = quantity * rate;
}
Now bind Click event of your Add & Minus Buttons to the method above in Form constructor.
public myForm(){
InitializeComponents();
Add.Click+=quantity_change;
Minus.Click+=quantity_change;
}
You can also use form's Load event if you want.
I've got a DataGidView that's very tight on horizontal space, and a column that has rather lengthy descriptions.
What I'd like to do is create a ComboBox column that:
Displays a shortened version of the description
Has the full description in the drop-down
Behind the scenes deals with the actual value
So for example, I have this contrived example below. The real list can have 20-30 items with text that's quite lengthy:
Code Short DropDown Text
1 BigBox Boxes larger than 6' x 6'
2 SmBox Boxes smaller than 6' x 6'
3 BigBrl Barrel 55 gallons
4 SmBrl Barrel less than 55 gallons
So what I want to show is:
When I open the dropdown I want to see:
And of course, when I query the value for the cell, I want "1".
I could split hairs and make the "short" description the first part of the longer description ("BigBx Boxes larger than 6' x 6'") but that doesn't seem right.
I'm looking for suggestions on the best way to accomplish this. No code yet to show, since I'm not quite sure where to start.
You'll want to change the DisplayMember of the combobox on the events DropDown and DropDownClosed.
On the Event DropDown, You'll want to change the DisplayMember to your Long Name. One thing I discovered is that changing the DisplayMember resets the SelectedIndex, so you'll want to save that and then reset it after you've changed the display.
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "LongNm";
comboBox1.SelectedIndex = i;
You'll want to repeat this process with the DropDownClosed event and the Short Name to return to your closed display.
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "ShortNm";
comboBox1.SelectedIndex = i;
The last problem I encountered was changing the width of the combobox dropdown. I found code to do that here.
The final code I had looks like this:
public partial class Form1 : Form
{
List<ComboData> data;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
data = new List<ComboData>();
data.Add(new ComboData(1, "BigBox", "Boxes larger than 6x6"));
data.Add(new ComboData(2, "SmBox", "Boxes small than 6x6"));
data.Add(new ComboData(3, "BirBrl", "Barrel 55 Gallons"));
data.Add(new ComboData(4, "smBrl", "Barrel less than 55 gallons"));
comboBox1.DataSource = data;
comboBox1.DisplayMember = "ShortNm";
}
private void comboBox1_DropDown(object sender, EventArgs e)
{
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "LongNm";
comboBox1.SelectedIndex = i;
ComboBox senderComboBox = (ComboBox)sender;
int width = senderComboBox.DropDownWidth;
Graphics g = senderComboBox.CreateGraphics();
Font font = senderComboBox.Font;
int vertScrollBarWidth =
(senderComboBox.Items.Count > senderComboBox.MaxDropDownItems)
? SystemInformation.VerticalScrollBarWidth : 0;
int newWidth;
foreach (ComboData s in ((ComboBox)sender).Items)
{
newWidth = (int)g.MeasureString(s.LongNm, font).Width
+ vertScrollBarWidth;
if (width < newWidth)
{
width = newWidth;
}
}
senderComboBox.DropDownWidth = width;
}
private void comboBox1_DropDownClosed(object sender, EventArgs e)
{
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "ShortNm";
comboBox1.SelectedIndex = i;
}
}
public class ComboData
{
public int Code;
public string ShortNm
{
get;
set;
}
public string LongNm
{
get;
set;
}
public ComboData(int c, string s, string l)
{
Code = c;
ShortNm = s;
LongNm = l;
}
}
The end result looks like this:
I think I have nearly the thing you want. One small compromise: Whilr you're editing the value, the combobox displays the truncated long value. Otherwise this should suit you well.
My Source code is a slightly edited version of this example. In the example they have an enum that is editable via a comboboxcolumn. My extension now changes the combobox to have a display and value member (the displaymember is the long text and the value member is the actual enum value) and when displaying the cell normally, the cell formatting event comes into play that overrides the display
These are the main changes:
private static T GetAttribute<T>(Enum value)
{
T attribute = value.GetType()
.GetMember(value.ToString())[0].GetCustomAttributes(typeof(T), false)
.Cast<T>()
.SingleOrDefault();
return attribute;
}
DataGridViewComboBoxColumn CreateComboBoxWithEnums()
{
DataGridViewComboBoxColumn combo = new DataGridViewComboBoxColumn();
//combo.DataSource = Enum.GetValues(typeof(Title));
var datatable = new DataTable(); //Setup a DataTable for your ComboBox Column
datatable.Columns.Add("col1", typeof(string));
datatable.Columns.Add("col2", typeof(Title));
foreach (Title item in Enum.GetValues(typeof(Title)))
datatable.Rows.Add(GetAttribute<DescriptionAttribute>(item).Description, item);
combo.DisplayMember = "col1";
combo.ValueMember = "col2";
combo.DataSource = datatable;
combo.DropDownWidth = 200;
combo.DataPropertyName = "Title";
combo.Name = "Title";
return combo;
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 0)//where Column1 is your combobox column
{
var val = dataGridView1[e.ColumnIndex, e.RowIndex].Value;
if (val != null)
{
e.Value = ((Title)val).ToString();
e.FormattingApplied = true;
}
}
}
public enum Title
{
[Description("This better be thy king!")]
King,
[Description("aka wanna-be-king")]
Sir
};
Full code: http://pastebin.com/Mp9DRvDn
You can use the click event or the dropdown event to detect when the user tries to open the combobox. Then display a list box with the longer strings right below the combobox. When the user selects a string from the listbox, set the corresponding value in the combobox.