Datagridview Dynamic cell calculation - c#

I have two columns in Datagridview, one for the price excluding Vat and another one for price including Vat, I want it to be dynamic, if I alter the price excluding vat it updates the column including Vat, and if I Update the including Vat column it updates the excluding VAT column vice-versa.
I would appreciate if anyone can help me with the right code for it in C#.
Here´s the code I´m using the calculation to one direction I need the code for the inverse.
private void dgv_Filho_CellEndEdit_1(object sender, DataGridViewCellEventArgs e)
{
bool Check = Convert.ToBoolean(dgv_Filho.CurrentRow.Cells["Check_Filho"].Value);
string Medida_1 = Convert.ToString(dgv_Filho.CurrentRow.Cells["Medida_1"].Value);
string Medida_2 = Convert.ToString(dgv_Filho.CurrentRow.Cells["Medida_2"].Value);
var Iva = Convert.ToDecimal(cb_Iva.Text);
if (Check)
{
if (!string.IsNullOrWhiteSpace(tb_CodigoArtigo.Text) || !string.IsNullOrWhiteSpace(tb_Descricao.Text))
{
dgv_Filho.CurrentRow.Cells["ArtigoPai"].Value = tb_CodigoArtigo.Text;
dgv_Filho.CurrentRow.Cells["Descricao_Pai"].Value = tb_Descricao.Text + " " + Medida_1 + Medida_2;
dgv_Filho.CurrentRow.Cells["CodigoArtigoFilho"].Value = tb_CodigoArtigo.Text + Medida_1 + Medida_2;
//dgv_Filho.CurrentRow.Cells["PrecoFilhoSemIva"].Value = tb_PVP1.Text;
decimal PrecoFilho = Convert.ToDecimal(dgv_Filho.CurrentRow.Cells["PrecoFilhoSemIva"].Value);
if (PrecoFilho > 0)
{
decimal PrecoFilhoComIva = PrecoFilho * Iva / 100 + PrecoFilho;
dgv_Filho.CurrentRow.Cells["PrecoFilhoComIva"].Value = PrecoFilhoComIva;
}
}
else
{
dgv_Filho.CurrentRow.Cells["ArtigoPai"].Value = string.Empty;
dgv_Filho.CurrentRow.Cells["Descricao_Pai"].Value = string.Empty;
}
}
}

This isn't too difficult using your existing code:
First of all, use the name of the edited column in if/else if statements to filter which conversion should take place, so that changing the VAT column doesn't get overwritten by the preVAT column. Then, use the opposite algebraic expression of the one you already have written to convert the postVAT price back to the preVAT
Here is what it will look like:
dgv_Filho.CurrentRow.Cells["ArtigoPai"].Value = tb_CodigoArtigo.Text;
dgv_Filho.CurrentRow.Cells["Descricao_Pai"].Value = tb_Descricao.Text + " " + Medida_1 + Medida_2;
dgv_Filho.CurrentRow.Cells["CodigoArtigoFilho"].Value = tb_CodigoArtigo.Text + Medida_1 + Medida_2;
decimal PrecoFilho = Convert.ToDecimal(dgv_Filho.CurrentRow.Cells["PrecoFilhoSemIva"].Value);
decimal PrecoFilhoComIva = Convert.ToDecimal(dgv_Filho.CurrentRow.Cells["PrecoFilhoComIva"].Value);
if (dgv_Filho.Columns[e.ColumnIndex].Name == "PrecoFilhoSemIva")
{
PrecoFilhoComIva = PrecoFilho * (Iva / 100) + PrecoFilho;
dgv_Filho.CurrentRow.Cells["PrecoFilhoComIva"].Value = PrecoFilhoComIva;
}
else if (dgv_Filho.Columns[e.ColumnIndex].Name == "PrecoFilhoComIva")
{
decimal PrecoFilhoSemIva = PrecoFilhoComIva - (PrecoFilhoComIva / (1 + (Iva / 100)) * (Iva / 100));
dgv_Filho.CurrentRow.Cells["PrecoFilhoSemIva"].Value = PrecoFilhoSemIva;
}
Using this code, editing the preVAT value will automatically update the postVAT value accordingly, and editing the postVAT value will automatically update the preVAT value accordingly

Rather than interact with the DataGridView directly (which can be complex) you could instead make a class that implements INotifyPropertyChanged and keeps all of its internal calculations up-to-date at all times (which is easier). Here is a simplified version of such a class that responds to changes of Descricao, Medida and PrecoFilhoSemIva.
Simplified class that represents a row of data
class Articulo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string _descricao = string.Empty;
public string Descricao
{
get => _descricao;
set
{
if (!Equals(_descricao, value))
{
_descricao = value;
OnPropertyChanged();
}
}
}
public string Descricao_Pai => $"{Descricao} {Medida_1}#{_precoFilhoSemIva.ToString("F2")}";
public decimal PrecoFilhoComIva => _precoFilhoSemIva * (1.0m + MainForm.Iva);
decimal _medida = 0;
public decimal Medida
{
get => _medida;
set
{
if (!Equals(_medida, value))
{
_medida = value;
OnPropertyChanged();
}
}
}
decimal _precoFilhoSemIva = 0;
public decimal PrecoFilhoSemIva
{
get => _precoFilhoSemIva;
set
{
if (!Equals(_precoFilhoSemIva, value))
{
_precoFilhoSemIva = value;
OnPropertyChanged();
}
}
}
string _codigoArtigo = System.Guid.NewGuid().ToString().Substring(0, 10).ToUpper();
public string CodigoArtigo
{
get => _codigoArtigo;
set
{
if (!Equals(_codigoArtigo, value))
{
_codigoArtigo = value;
OnPropertyChanged();
}
}
}
}
Instances of this class are placed in a BindingList which is assigned to the DataSource property of dgv_Filho and caused the DGV to update whenever the Refresh method is called.
Initializations
The only interaction that should be necessary with the DGV is to initialize the columns and bindings properly in the MainForm override for the Load event. This is also where we bind the combo box to a static value for Iva that can be used by the calculation for the row items.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
initDataGridView();
initComboBox();
}
private void initDataGridView()
{
dgv_Filho.DataSource = DataSource;
DataSource.ListChanged += (sender, e) =>
{
if (e.ListChangedType == ListChangedType.ItemChanged)
{
dgv_Filho.Refresh();
}
};
// Add one or more items to autogenerate the columns.
Random randomPriceGen = new Random(1);
for (int i = 1; i <= 3; i++)
{
var preco = i == 1 ? 1.0m : (decimal)randomPriceGen.NextDouble() * 100;
DataSource.Add(new Articulo
{
Descricao = $"Articulo {(char)('A' + (i - 1))}",
Medida = i,
PrecoFilhoSemIva = preco,
});
}
// Do a little column formatting
foreach (DataGridViewColumn column in dgv_Filho.Columns)
{
switch (column.Name)
{
case nameof(Articulo.Descricao):
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
column.MinimumWidth = 120;
break;
case nameof(Articulo.Medida):
case nameof(Articulo.PrecoFilhoSemIva):
case nameof(Articulo.PrecoFilhoComIva):
column.DefaultCellStyle.Format = "F2";
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
break;
default:
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
break;
}
}
}
private void initComboBox()
{
cb_Iva.SelectedIndex = 0;
cb_Iva.SelectedIndexChanged += onIvaSelected;
cb_Iva.KeyDown += (sender, e) =>
{
if( e.KeyData == Keys.Enter)
{
e.Handled = e.SuppressKeyPress = true;
}
onIvaSelected(sender, e);
};
onIvaSelected(cb_Iva, EventArgs.Empty);
void onIvaSelected(object sender, EventArgs e)
{
if (decimal.TryParse(cb_Iva.Text.Replace("%", string.Empty), out decimal iva))
{
Iva = iva / 100m;
dgv_Filho.Refresh();
cb_Iva.BackColor = SystemColors.Window;
}
else cb_Iva.BackColor = Color.LightSalmon;
}
}

Related

Formatting data inside WPF DataGrid

I am trying to format my cells inside wpf DataGrid control but i am having problems with it.
I am having class like this:
class WashItem
{
private DateTime _Time = DateTime.Now;
private string _Staff = "Undefined";
private double _Price = 850;
public string Staff { get => _Staff; set => _Staff = value; }
public DateTime Time { get => _Time; set => _Time = value; }
public double Price { get => _Price; set => _Price = value; }
}
and I am populating my datagrid like this
private void ReloadData()
{
string[] lines = File.ReadAllLines("baza.txt");
double ukupnaVrednost = 0;
DataTable table = new DataTable();
DataColumn col1 = new DataColumn("Osoblje");
DataColumn col2 = new DataColumn("Vreme");
DataColumn col3 = new DataColumn("Cena");
table.Columns.Add(col1);
table.Columns.Add(col3);
table.Columns.Add(col2);
for (int i = 0; i < lines.Length; i++)
{
WashItem item = JsonConvert.DeserializeObject<WashItem>(lines[i]);
DataRow row = table.NewRow();
row["Osoblje"] = item.Staff;
row["Vreme"] = item.Time;
row["Cena"] = item.Price;
table.Rows.Add(row);
ukupnaVrednost += item.Price;
}
dataGridView.ItemsSource = table.AsDataView();
UkupnoOprano.Content = "Ukupno oprano vozila: " + lines.Length;
UkupnoOpranoVrednost.Content = "Vrednost: " + ukupnaVrednost.ToString("#,##0.00");
}
then I have created datagrid like this
<DataGrid Name="dataGridView" AutoGenerateColumns="true" AutoGeneratingColumn="dataGridView_AutoGeneratingColumn"></DataGrid>
and finally here is my dataGridView_AutoGeneratingColumn function
private void dataGridView_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if(e.PropertyName == "Cena")
{
((DataGridTextColumn)e.Column).Binding.StringFormat = "#,##0.00 rsd";
}
else if(e.PropertyName == "Vreme")
{
((DataGridTextColumn)e.Column).Binding.StringFormat = "dd/MM/yyyy";
}
}
Auto generate columns function get's fired and it does enter if block but at the end my data is still the same - not formatted.
I would say you need to set DataType for DataColumns, because required formats are specific to those types, and won't work for object type.
DataTable table = new DataTable
{
Columns =
{
new DataColumn("Osoblje"),
new DataColumn("Vreme", typeof(DateTime)),
new DataColumn("Cena", typeof(double))
}
};

How do I map an array field to multiple columns in a WinForms DataGridView using a DataSource binding?

I have a class like this:
public class PricingRecord
{
private string _name;
private float[] _prices;
public PricingRecord(string name, float[] prices)
{
_name = name;
_prices = prices;
}
[DisplayName("Name")]
public string PricingName => _name;
public float[] Prices => _prices;
}
I want to map "Prices" to "Price 1", "Price 2", etc. columns in a DataGridView. I can get the columns to appear, but don't know how to make the mapping work.
Here's the main window:
public partial class MainForm : Form
{
private int _numPricingColumns;
private BindingList<PricingRecord> _records;
public MainForm()
{
InitializeComponent();
SetupPricingData();
SetupGridView();
}
private void SetupPricingData()
{
_records = new BindingList<PricingRecord>();
for (int i = 0; i < 100; i++)
{
var pricing = new PricingRecord($"Name {i + 1}", new [] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f });
_records.Add(pricing);
}
_numPricingColumns = _records[0].Prices.Length;
GridView.DataSource = _records;
}
private void SetupGridView()
{
//GridView.AutoGenerateColumns = false;
//DataGridViewColumn column = new DataGridViewTextBoxColumn
//{
// Name = "Name",
// DataPropertyName = "PricingName"
//};
//GridView.Columns.Add(column);
for (int i = 0; i < _numPricingColumns; i++)
{
DataGridViewColumn column = new DataGridViewTextBoxColumn
{
Name = $"Price {i + 1}",
DataPropertyName = $"Price{i + 1}"
};
GridView.Columns.Add(column);
}
}
}
If I don't use BindingView I can set it up all by hand, but then I have to maintain the rows and columns myself. I'd like to use BindingView but this doesn't seem to support mapping many columns to an array.
I found I could get the pricing columns to be handled by drawing them. The use I needed was readonly so this is my solution for now:
private void OnCellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
for (int i = 0; i < _numPricingColumns; i++)
{
var name = $"Price {i + 1}";
var column = GridView.Columns[name];
if (column.Index == e.ColumnIndex && e.RowIndex >= 0)
{
var row = GridView.Rows[e.RowIndex];
var record = row.DataBoundItem as PricingRecord;
if (record != null)
{
var selected = (e.State & DataGridViewElementStates.Selected) != 0;
using (var brush = new SolidBrush(selected ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor))
{
e.PaintBackground(e.ClipBounds, selected);
e.Graphics.DrawString(record.Prices[i].ToString("N"), e.CellStyle.Font, brush,
e.CellBounds.X + 2.0f, e.CellBounds.Y + 2.0f, StringFormat.GenericDefault);
e.Handled = true;
}
}
}
}
}
A faster and more flexible solution proved to be dropping BindingList and directly poking the cells in the grid. The need to map an array field to multiple columns becomes moot as it's done by hand.

Gridview get difference between two sums

I'm using devexpress XtraGrid control. My problem is the following: I want to get the sum of the first column, and then the second column. I eventually want to subtract the sum of the first column of the sum of the second columns and to display the grid in the footer ...
Sum1Columns - Sum2Columns = balance
And then show balance on data grid control - footer (below the 1column)
dgvVIEW.Columns(1).Name = "PROMDUGU"
dgvVIEW.Columns(1).Caption = "1COLUMN"
dgvVIEW.Columns(1).Visible = True
dgvVIEW.Columns(1).DisplayFormat.FormatType = FormatType.Numeric
dgvVIEW.Columns(1).DisplayFormat.FormatString = "c2"
dgvVIEW.Columns(1).SummaryItem.SummaryType = DevExpress.Data.SummaryItemType.Custom
dgvVIEW.Columns(1).SummaryItem.SummaryType = DevExpress.Data.SummaryItemType.Sum
dgvVIEW.Columns(1).SummaryItem.DisplayFormat = "SUM= {0:n2}"
dgvVIEW.Columns(2).Name = "PROMPOTR"
dgvVIEW.Columns(2).Caption = "2COLUMN"
dgvVIEW.Columns(2).Visible = True
dgvVIEW.Columns(2).DisplayFormat.FormatType = FormatType.Numeric
dgvVIEW.Columns(2).DisplayFormat.FormatString = "c2"
dgvVIEW.Columns(2).SummaryItem.SummaryType = DevExpress.Data.SummaryItemType.Sum
dgvVIEW.Columns(2).SummaryItem.DisplayFormat = "Sum= {0:n2}"
Add another summary field to 2nd column and set its type to custom.
dgvVIEW.Columns(2).Summary.Add(new GridColumnSummaryItem(SummaryItemType.Custom, "customBalance", "Balance= {0:c2}"));
Then handle CustomSummaryCalculate event.
private void dgvVIEW_CustomSummaryCalculate(object sender, CustomSummaryEventArgs e) {
if (e.SummaryProcess == CustomSummaryProcess.Start) {
this.sum1 = 0; // <--- class member !
this.sum2 = 0; // <--- class member !
return;
}
if (e.SummaryProcess == CustomSummaryProcess.Calculate) {
if (e.Item.FieldName == "PROMDUGU" {
this.sum1 += Convert.ToDecimal(e.FieldValue);
return;
}
if (e.Item.FieldName == "PROMPOTR" {
this.sum2 += Convert.ToDecimal(e.FieldValue);
return;
}
return;
}
if (e.SummaryProcess == CustomSummaryProcess.Finalize && e.Item.FieldName == "customBalance") {
e.TotalValue = sum1 - sum2;
}
}

Textbox display formatting

I want to add "," to after every group of 3 digits. Eg : when I type 3000000 the textbox will display 3,000,000 but the value still is 3000000.
I tried to use maskedtexbox, there is a drawback that the maskedtexbox displayed a number like _,__,__ .
Try adding this code to KeyUp event handler of your TextBox
private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
if (!string.IsNullOrEmpty(textBox1.Text))
{
System.Globalization.CultureInfo culture = new System.Globalization.CultureInfo("en-US");
int valueBefore = Int32.Parse(textBox1.Text, System.Globalization.NumberStyles.AllowThousands);
textBox1.Text = String.Format(culture, "{0:N0}", valueBefore);
textBox1.Select(textBox1.Text.Length, 0);
}
}
Yes, it will change the value stored in a texbox, but whenever you need the actual number you can use the following line to get it from the text:
int integerValue = Int32.Parse(textBox1.Text, System.Globalization.NumberStyles.AllowThousands);
Of course do not forget to check that what the user inputs into the textbox is actually a valid integer number.
Use String.Format
int value = 300000
String.Format("{0:#,###0}", value);
// will return 300,000
http://msdn.microsoft.com/en-us/library/system.string.format.aspx
This may work fine for your scenario I hope.
private string text
{
get
{
return text;
}
set
{
try
{
string temp = string.Empty;
for (int i = 0; i < value.Length; i++)
{
int p = (int)value[i];
if (p >= 48 && p <= 57)
{
temp += value[i];
}
}
value = temp;
myTxt.Text = value;
}
catch
{
}
}
}
private void digitTextBox1_TextChanged(object sender, EventArgs e)
{
if (myTxt.Text == "")
return;
int n = myTxt.SelectionStart;
decimal text = Convert.ToDecimal(myTxt.Text);
myTxt.Text = String.Format("{0:#,###0}", text);
myTxt.SelectionStart = n + 1;
}
Here, myTxt = your Textbox. Set Textchanged event as given below and create a property text as in the post.
Hope it helps.
You could hook up to OnKeyUp event like this:
private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
if (!(e.KeyCode == Keys.Back))
{
string text = textBox1.Text.Replace(",", "");
if (text.Length % 3 == 0)
{
textBox1.Text += ",";
textBox1.SelectionStart = textBox1.Text.Length;
}
}
}
Get Decimal Value Then set
DecimalValue.ToString("#,#");

Only top row of DataGridView updating?

I have a DataGridView that I'm populating from a list. The function that edits this list is called LoadCollectionData()'. Extra rows get added to the list just fine, and the relevant data pertaining to that row populates when the row is added.
The problem is that later on when other data is being changed that'd alter what's displayed on the datagrid, only the top row continues to update, all of the others remain the same.
Here's the code for the method:
public bool haschanged = false;
public class KeywordDensity
{
public bool included { get; set; }
public string keyword { get; set; }
public string occurences { get; set; }
public string density { get; set; }
}
public int WordCount(string txtToCount)
{
string pattern = "\\w+";
Regex regex = new Regex(pattern);
int CountedWords = regex.Matches(txtToCount).Count;
return CountedWords;
}
public int KeywordCount(string txtToCount, string pattern)
{
Regex regex = new Regex(pattern);
int CountedWords = regex.Matches(txtToCount).Count;
return CountedWords;
}
public List<KeywordDensity> LoadCollectionData()
{
string thearticle = txtArticle.Text.ToLower();
string keywordslower = txtKeywords.Text.ToLower();
string[] keywordsarray = keywordslower.Split('\r');
List<KeywordDensity> lsikeywords = new List<KeywordDensity>();
bool isincluded = false;
double keywordcount = 0;
double wordcount = WordCount(thearticle);
double thedensity = 0;
foreach (string s in keywordsarray)
{
if (s != "")
{
keywordcount = KeywordCount(thearticle, s);
thedensity = keywordcount / wordcount;
thedensity = Math.Round(thedensity, 4) * 100;
if (thearticle.Contains(s))
{
isincluded = true;
}
else
{
isincluded = false;
}
lsikeywords.Add(new KeywordDensity()
{
included = isincluded,
keyword = s,
occurences = keywordcount.ToString(),
density = thedensity.ToString() + "%"
});
}
}
return lsikeywords;
}
private void txtArticle_TextChanged(object sender, EventArgs e)
{
if (haschanged == false)
haschanged = true;
lblWordCountNum.Text = WordCount(txtArticle.Text).ToString();
dataGrid.DataSource = LoadCollectionData();
}
private void dataGrid_MouseUp(object sender, MouseEventArgs e)
{
int cursorpos = 0;
string copied = "";
if (dataGrid.CurrentCellAddress.X == 1) //Only grab content if the "Keyword" column has been clicked on
copied = " " + dataGrid.CurrentCell.Value.ToString() + " ";
cursorpos = txtArticle.SelectionStart;
txtArticle.Text = txtArticle.Text.Insert(cursorpos, copied);
}
What's even more odd, is that when I click on any of the rows, then they immediately update. However, unless the row is clicked on (unless it's the top one) it doesn't update.
Because of this, I suspect there may be some property I need to set on the dataGrid itself, or I need to somehow tell each row to refresh through code.
What's the dealio?
EDIT: It appears that the only reason that the cell that's clicked on updates is because I actively grab content from the cell. I commented out the code below and it stopped updating even when clicked on. It then would only update the top row's values and that's it.
Code:
//Moved above in EDIT 3
EDIT 2: Here's the class declaration for KeywordDensity:
//Moved above in EDIT 3
EDIT 3: Posted whole schebang.
I modified the code slightly, try this code.
string[] keywordsarray = keywordslower.Split
(new char[] {'\r','\n' }, StringSplitOptions.RemoveEmptyEntries);
You may need to Invalidate() the control to trigger a repaint.
call the DataBind() method of the datagrid. That should do.
Update
There's a ResetBindings() in that case.

Categories

Resources