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;
}
}
Related
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;
}
}
I'm trying to turn textboxes and buttons visible when the number of tracks it's selected in a combobox.
For example: when I select 3, just 3 textboxes and the 3 respective buttons to select the tracks are enabled. How can I change this code that I've made to a simple foreach or a for?
if (numero_faixas == 1) {
txtFaixa1.Visible = true;
btnFaixa1.Visible = true;
} else if (numero_faixas == 2) {
txtFaixa1.Visible = true;
btnFaixa1.Visible = true;
txtFaixa2.Visible = true;
btnFaixa2.Visible = true;
} else if (numero_faixas == 3) {
txtFaixa1.Visible = true;
btnFaixa1.Visible = true;
txtFaixa2.Visible = true;
btnFaixa2.Visible = true;
txtFaixa3.Visible = true;
btnFaixa3.Visible = true;
}
You can reduce the lines of code by changing your conditions, so you don't have to reference the same control so many times:
if (numero_faixas > 0)
{
txtFaixa1.Visible = true;
btnFaixa1.Visible = true;
}
if (numero_faixas > 1)
{
txtFaixa2.Visible = true;
btnFaixa2.Visible = true;
}
if (numero_faixas > 2)
{
txtFaixa3.Visible = true;
btnFaixa3.Visible = true;
}
To use a foreach loop, you could cast the Controls collection to an IEnumerable<Control> and then, using System.Linq;, you can filter on controls of type TextBox and Button, where the control name contains "Faxia". Then, in the loop body, we can use int.TryParse to try to convert the last character of the control name to an int, and if that succeeds, then set the control to Visible if the control number is less than numero_faixas + 1:
foreach (Control control in Controls.Cast<Control>()
.Where(c => (c is Button || c is TextBox) && c.Name.Contains("Faixa")))
{
// Get the number associated with this control and compare it to numero_faixas
int controlNumber;
if (int.TryParse(control.Name.Substring(control.Name.Length - 1), out controlNumber) &&
controlNumber < numero_faixas + 1)
{
control.Visible = true;
}
}
Here's a proof of concept that you could respin using your business rules.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ShowHideButtons_47439046
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitOurThings();
}
private void InitOurThings()
{
//lets create a combo box with options to select
ComboBox combo = new ComboBox();
combo.Location = new Point(5, 5);//place it somewhere
//add selectable items
for (int i = 0; i < 10; i++)
{
combo.Items.Add(i);
}
combo.SelectedValueChanged += Combo_SelectedValueChanged;//the event which will handle the showing/hidding
Controls.Add(combo);//add the combo box to the form
//lets create some buttons and textboxes
int btnx = 5;
int btny = combo.Height + combo.Location.Y + 5;
for (int i = 0; i < 10; i++)
{
Button btn = new Button();
btn.Location = new Point(btnx, btny);
btn.Name = i.ToString();
btn.Text = i.ToString();
Controls.Add(btn);
btny += btn.Height + 5;
TextBox txtbx = new TextBox();
txtbx.Location = new Point(btn.Location.X + btn.Width + 5, btn.Location.Y);
txtbx.Name = i.ToString();
txtbx.Text = i.ToString();
Controls.Add(txtbx);
}
}
private void Combo_SelectedValueChanged(object sender, EventArgs e)
{
int selectedValue = int.Parse(((ComboBox)sender).SelectedItem.ToString());
foreach (Control item in Controls)
{
//show/hide the controls based on their Name being Equal Or Smaller than the selectedItem
if (item is TextBox)
{
int itemNumber = int.Parse(item.Name);
item.Visible = itemNumber <= selectedValue ? true : false;
}
if (item is Button)
{
int itemNumber = int.Parse(item.Name);
item.Visible = itemNumber <= selectedValue ? true : false;
}
}
}
}
}
C# TextBox
AutoCompleteCustomSource has a List<string>,
AutoCompleteMode = Suggest.
I can see the List when I type a Letter.
How to show entire list without Typing a Letter Programmatically? This must be done while the User presses the Down Arrow Key in the TextBox.
Is there any Win32 API Available?
My Solution
I refined a Better Solution.
Add a ListBox Control to the form and make it as Visible = false
int curSelIndex = -1;
The below given Code will be executed Form_Load Event.
txtEmpId.AutoCompleteCustomSource.AddRange(EmpIds.ToArray());
lstAutoComplete.Items.Clear();
lstAutoComplete.Items.AddRange(EmpIds.ToArray());
txtEmpId.KeyDown += (ks, ke) =>
{
if (!(ke.KeyCode == Keys.Down ||
ke.KeyCode == Keys.Up ||
ke.KeyCode == Keys.Enter))
{
lstAutoComplete.Visible = false;
return;
}
ke.Handled = true;
if (ke.KeyCode == Keys.Enter)
{
if (lstAutoComplete.Visible)
{
var str = lstAutoComplete.SelectedItem + "";
// Process the Selected Item and set to TextBox.
}
}
if (!lstAutoComplete.Visible && txtEmpId.Focused)
{
var loc = txtEmpId.Location;
loc.Y += txtEmpId.Height;
lstAutoComplete.Location = loc;
lstAutoComplete.Size = txtEmpId.Size;
lstAutoComplete.Height = 100;
lstAutoComplete.SelectedIndex = 0;
curSelIndex = 0;
lstAutoComplete.Visible = true;
}
else if(lstAutoComplete.Visible && txtEmpId.Focused)
{
if (ke.KeyCode == Keys.Down)
{
curSelIndex++;
if (curSelIndex >= lstAutoComplete.Items.Count)
curSelIndex = lstAutoComplete.Items.Count - 1;
if (lstAutoComplete.Items.Count > 0)
lstAutoComplete.SelectedIndex = curSelIndex;
}
else if (ke.KeyCode == Keys.Up)
{
curSelIndex--;
if (curSelIndex < 0)
curSelIndex = 0;
if (lstAutoComplete.Items.Count > 0)
lstAutoComplete.SelectedIndex = curSelIndex;
}
}
};
txtEmpId.Leave += (ls, le) => lstAutoComplete.Visible = false;
I didn't find any API for your problem, so I just make a my own suggestion box by using ListBox to show when the Down Arrow Key is pressed, when you do other operation, it disappeares. I hope it is useful to you. code sample is bellow:
//string datasource
List<string> strList = null;
//suggestion listbox
ListBox sugBox = null;
public FrmTextSuggest()
{
InitializeComponent();
//setting the textbox control
strList = new List<string>()
{
"USA",
"England",
"China",
"Japan",
"Korea",
"India",
"France",
"Canada"
};
var autoCollection = new AutoCompleteStringCollection();
autoCollection.AddRange(strList.ToArray());
this.txtCountry.AutoCompleteCustomSource = autoCollection;
this.txtCountry.AutoCompleteMode = AutoCompleteMode.Suggest;
this.txtCountry.AutoCompleteSource = AutoCompleteSource.CustomSource;
//register the Down Arrow Key event
this.txtCountry.KeyDown += new KeyEventHandler(txtCountry_KeyDown);
}
void txtCountry_KeyDown(object sender, KeyEventArgs e)
{
//show the your own suggestion box when pressing down arrow and the text box is empty
if (e.KeyCode == Keys.Down && txtCountry.Text.Trim().Equals(""))
{
sugBox = new ListBox();
//define the box
sugBox.Width = txtCountry.Width;
Point p = txtCountry.Location;
p.Y += txtCountry.Height;
sugBox.Location = p;
sugBox.Items.AddRange(strList.ToArray());
//copy the value to the textbox when selected index changed.
sugBox.SelectedIndexChanged += new EventHandler(sugBox_SelectedIndexChanged);
//show box
if (sugBox.Items.Count > 0)
{
sugBox.SelectedIndex = 0;
this.Controls.Add(sugBox);
sugBox.Focus();
}
}
//remove and hide your own suggestion box when other operation
else
{
if (sugBox != null && this.Controls.Contains(sugBox))
{
this.Controls.Remove(sugBox);
sugBox.Dispose();
sugBox = null;
}
}
}
void sugBox_SelectedIndexChanged(object sender, EventArgs e)
{
string selText = this.sugBox.SelectedItem.ToString();
if (!string.IsNullOrEmpty(selText))
{
this.txtCountry.Text = selText;
}
}
here is my result of test:
I have two datagridview.
With same column headers but different cell data.
first One is called grid_db
second one is calld grid_statement.
If the value of grid_db is not same as that of grid_statement at cell[j] i must have the cells highlighted (red).
i tried the following
int no_of_col = grid_db.Columns.Count;
int j;
for (j = 0; j < no_of_col;)
{
//if statement value is null replace with ZERO
if (grid_statement.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_statement.Rows[0].Cells[j].Value.ToString()))
{
B = grid_statement.Rows[0].Cells[j].Value.ToString();
}
//if db value is null replace with zero
if (grid_db.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_db.Rows[0].Cells[j].Value.ToString()))
{
A = grid_db.Rows[0].Cells[j].Value.ToString();
}
if (A != B)
{
grid_db.Rows[0].Cells[j].Style.BackColor = Color.Red;
grid_statement.Rows[0].Cells[j].Style.BackColor = Color.Red;
j++;
}
}
But it does not works.The above codes highlights ALL the columns of both grids.
Help ?
I tried your code, and it works for me, the only thing i've changed is the for loop to increment on every pass, otherwise it can easily be infinite (it only works for 1st row because that's what your code does):
public Form1()
{
InitializeComponent();
grid_db.DataSource = new[]
{
new{
id = 1,
tekst="a"
},
new
{
id=2,
tekst="b"
}
}.ToList();
grid_statement.DataSource = new[]
{
new{
id = 1,
tekst="b"
},
new
{
id=2,
tekst="c"
}
}.ToList();
Load += (sender, args) =>
{
HighlightRows();
};
}
private void HighlightRows()
{
int no_of_col = grid_db.Columns.Count;
int j;
var B = "";
var A = "";
for (j = 0; j < no_of_col; j++)
{
//if statement value is null replace with ZERO
if (grid_statement.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_statement.Rows[0].Cells[j].Value.ToString()))
{
B = grid_statement.Rows[0].Cells[j].Value.ToString();
}
//if db value is null replace with zero
if (grid_db.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_db.Rows[0].Cells[j].Value.ToString()))
{
A = grid_db.Rows[0].Cells[j].Value.ToString();
}
if (A != B)
{
grid_db.Rows[0].Cells[j].Style.BackColor = Color.Red;
grid_statement.Rows[0].Cells[j].Style.BackColor = Color.Red;
}
}
}
var differentCells =
grid_db.Rows.OfType<DataGridViewRow>()
.SelectMany(r=>r.Cells.OfType<DataGridViewCell>())
.Where(c=>!grid_statement[c.ColumnIndex,c.RowIndex].Value.Equals(c.Value));
//loop through all the cells and make them red
foreach(var cell in differentCells)
cell.Style.BackColor = Color.Red;
Hello I am having trouble with an iteration through a list of 17 labels:
for (int i = 0; i < labels.Count - 1; i++)
{
MessageBox.Show(labels[i].Name);
if (labels[i].Visible == false && labels[i + 1].Visible == true)
{
...
Here are the results I get:
First it goes from label10 to label17, and then in descending order from label9 to label2.
Here is how I add the labels to the list:
private void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
if (c is Label)
{
labels.Add(c);
c.Enabled = true;
if (c.Visible == false)
{
c.Visible = true;
}
}
}
}
I want it to go from label1 to label16, since the loop is just a loop I guess the problem lies in the order in which the labels were added to the list, but I am not sure how to fix it.
Your main problem is lexicographic order which is inherently used when you sort by Name of the label, what you want is to sort by numbers after the term label. In that case, first sort the labels list and then run the for statement over it, check the code:
var lst = labels.OrderBy(x => int.Parse(x.Name.Substring("label".Length))).ToList();
for (int i = 0; i < lst.Count - 1; i++)
{
MessageBox.Show(lst[i].Name);
...
But have in mind that this code is simple and presumes that label Name property always starts with "label" string. If that can change you must handle that case.
I guess you want to sort the labels according to their names?
labels.Sort((x, y) => { return x.Name.CompareTo(y.Name); });
but what are the difference between:
Show "Label 1" first, then "Label 2", and
Show "Label 2" first, then "Label 1"?
Check the designer.cs file to see in which order the labels are added to the Form
assuming that you have Labels id as Label1,Label2..........,Label16
in order to get the labels serially you have to write the following code
labels = labels.ConvertAll<Control>(GetIdFromLabel);
labels.Sort((x, y) => { return x.Id.CompareTo(y.Id); });
public Control GetIdFromLabel(Control c)
{
c.Id = c.Name.Replace("Label", "") == "" ? 0 : Convert.ToInt32(c.Name.Replace("Label", ""));
return c;
}
add this class in your code also
public class Control
{
public string Name { get; set; }
public int Id { get; set; }
}
Try this out:
private void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
labels.Clear();
Control[] matches;
for (int i = 1; i <= 16; i++)
{
matches = this.Controls.Find("label" + i.ToString(), true);
if (matches.Length > 0 && matches[0] is Label)
{
Label lbl = (Label)matches[0];
labels.Add(lbl);
lbl.Enabled = true;
if (lbl.Visible == false)
{
lbl.Visible = true;
}
}
}
}