How to connect Tab Controls - c#

my winForms app has a tab control which consists of two tabs (tab1 & tab2). In tab2 data is fetched in a datagridview fron a database(Product infomations).
In tab1, I've a combobox [sales analyse]which makes a user to select an option.
I now want to get access to tab2 from tab1 on cb selection, displaying me a regional sales information from the data in tab2 datagrid.
Is it possible? I don't really know wher to start
tab1 image
tab2
Expectation:
if the combobox in tab1 is selected, it should then look through the datagridview in tab2 where the (regions) North, East, West ect are and then sum the sale 13, sales 14 .. and display in the textBoxes respectively.

As your controls all sit in one Form their methods can all reference each other without any additional help.
So you can write in the SelectedIndexChanged of the ComboBox cbAnalyse
cbAnalyse_SelectedIndexChanged(object sender, EventArgs e)
{
if (cbAnalyse.SelectedItem.ToStringndex == "Sales Analysis"
{
someTextbox1.Text = ColumnSum(yourDataGridView, someColumn1) + "$";
someTextbox2.Text = ColumnSum(yourDataGridView, someColumn2) + "$";
}
This uses a small helper function, which sums up all values from one column in a DataGridView:
decimal ColumnSum(DataGridView dgv, int columnIndex)
{
decimal sum = 0m;
for (int row = 0; row < DGV.Rows.Count; row++)
if (DGV[columnIndex, row].Value != null) sum += Convert.ToDecimal(DGV[1, row].Value);
return sum;
}
Folks often run into problems when they need to refer to controls that are not sitting in either the same Form but in a 2nd, 3rd etc Form. Or when they are part of a Usercontrol, which is a custome container for holding controls.
In both cases those controls are by default private members of the other Forms or of the UserObject.
In these cases one needs to create some kind of public accessor to them, usually by a Property. And in the case of Forms, one also need to provide a reference to the other forms, often stored when opening them.
In this case, the 2nd Form often also needs a back-refrence to the 1st Form; this is often pass in in the constructor.
But in your case none of these complications matter. All you need is the patience to wire up all those TextBoxes ;-)
Update: As you also seem to have a problem getting the intermediate sums and need to allow for repeating regions in the rows of Tab 2, you also want to use a function that will calculate with a where clause:
decimal ColumnSumWhere(DataGridView dgv, int columnIndex, string region)
{
decimal sum = 0m;
for (int row = 0; row < DGV.Rows.Count; row++)
if (DGV[columnIndex, row].Value != null) &&
(DGV[regionColumn, row].Value.ToString() == region)
sum += Convert.ToDecimal(DGV[1, row].Value);
return sum;
}

If I got it right, whenever you change the value in sales analyze combo, the tab page containing data grid should should be activate.
You can set the selected index of the tab control to the the data grid tab, and it should work
this.tabControl1.SelectedIndex = 1;//Index of data grid tab

Related

Current cell cannot be set to an invisible cell in DataGridView WITHOUT DataSource

I have a dgv C# DataGridView built without DataSource.
I have a column with index c that is set to invisible when the value of a cell of another column changes to a certain value (within the CellValueChanged event handler):
dgv.Columns[c].Visible = false;
The CellValueChanged event handler is called after modifying the cell and then clicking on another cell. The problem is that when I click on the column which is about to be set to invisible I get the exception "Current cell cannot be set to an invisible cell", whereas everything is fine if I click on one of the other columns.
I read some other answers (e.g. this or this) where using CurrencyManager.SuspendBinding and CurrencyManager.ResumeBindingis suggested. However that doesn't work for me because the DataSource is null.
Any hint?
Thank you.
The problem with setting the column to “not visible” in the grids CellValueChanged event is that while the scope of execution is in the CellValueChanged event, the grid is NOT going to give you ANY information about “where” the user clicked/moved “to” after the cells value changed.
This alone is ALWAYS going to leave hiding the column (in that event) prone to errors since we do not know “where” the user clicked/moved. Because of this it would appear obvious that if we want to avoid the error we are going to have to “hide” the column in some other event.
Another issue you may run into is that some events don’t like it when you try to “change” the grids “selected” cell, which is what we are looking to do when the user clicks on the column we want to make invisible. We need to “change” the selected cell to some other cell to avoid the error.
Given this, below is a hacky approach that may help. Be aware that there was NOT a lot of testing on the code and one issue/error occurred when the user clicked on the column header (to sort) for the first column. This caused a crash and I ended up turning the “sorting” off for that column.
The small example below uses this logic... A DataGridView with four (4) text columns is set on a form and filled with some data. ConditionColumn (column 0) in the grid is the “condition” column and it has string values from 0-19. It is this column that will trigger “when” to “hide” the HideColumn (column 2).
IF a value greater than twenty (20) is entered into any cell in the ConditionColumn, THEN the HideColumn will be set as invisible. There is a small function AllCellsLessThan20 that loops through all the rows in the grid and checks the ConditionColumn cells for any values that are greater than 20. If at least one cell value is greater than 20, then the function will return false. This is used to turn the hidden column on or off such that if ALL cells in the ConditionColumn are less than 20, then the column will be shown. Otherwise, if one or more cells is greater than 20, then the column will be hidden.
To avoid the error described, the code will set the column to invisible in the grids SelectionChanged event. This event will fire “after” the CellValueChanged event if the user selects another cell by any means… click, tab or arrow keys. We will check several things to determine if we need to hide or show the column.
Obviously since this event is called AFTER the CellValueChange event fired, the grid is not going to give us ANY information about “where” the previously selected cell was. Hence, the global variables. Setting these global values in the CellValueChanged event will make them available in the SelectionChanged event.
Maybe some variables like:
ColumnShown : bool to indicated if the column is currently hidden or shown.
ValueGreaterThan20: bool to indicate if any value in the ConditionColumn is greater than 20
and PreviousRow : int to indicate the row index of the last “Changed” cell.
bool ColumnShown = true;
bool ValueGreaterThan20 = false;
int PreviousRow = 0;
Set the grids columns with some useful names to avoid any indexing problems, then fill the grid with some test data.
public Form1() {
InitializeComponent();
dataGridView1.Columns[0].Name = "ConditionColumn";
dataGridView1.Columns[0].HeaderText = "Condition";
dataGridView1.Columns[2].Name = "HideColumn";
dataGridView1.Columns[2].HeaderText = "Hide Column";
}
private void Form1_Load(object sender, EventArgs e) {
FillGrid();
}
private void FillGrid() {
for (int i = 0; i < 20; i++) {
dataGridView1.Rows.Add(i, "C1R" + i, "C2R" + i, "C3R" + i);
}
}
Next is a function to check if any value in the ConditionColumn is greater than 20.
private bool AllCellsLessThan20() {
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (row.Cells[0].Value != null) {
string sValue = row.Cells["ConditionColumn"].Value.ToString();
if (int.TryParse(sValue, out int value)) {
if (value > 20) {
return false;
}
}
}
}
return true;
}
Next, is the grids CellValueChanged event. Here we only need to check two things: 1) was the value changed in column 0 ConditionColumn and since a value did change in that column we need to check ALL the values. If any value is over 20, then set the variable ValueGreaterThan20 to true otherwise false. Also, we will save the row index (PreviousRow) of the changed cell since we will want to use it in the SelectionChanged event.
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (e.ColumnIndex == 0) {
if (AllCellsLessThan20())
ValueGreaterThan20 = false;
else
ValueGreaterThan20 = true;
}
PreviousRow = e.RowIndex;
}
The SelectionChanged event is below. I am aware this could be more compact. First a check is made to see if the column is shown or hidden.
If the column is already “hidden”, then we just need to check to make sure at least one value in the ConditionColumn is greater than 20 and simply leave it hidden. If all values are less than 20 then we want to un-hide the column.
If the column is NOT “hidden,” then we check to see if any values are greater than 20. If no values are greater than 20, then simply leave the column visible.
If the column is NOT “hidden,” and one or more values are greater than 20, then we need to check to see if the current selection is the column we want to hide. If the current selection is NOT the column we want to hide, then simply hide it since we know this will not throw an error.
Lastly, if the column is NOT “hidden” AND there is a value greater than 20 AND the current selection IS the column we want to hide. In this case the code will set/change the CurrentCell to the first cell in the PreviousRow. Then set the column to invisible and set the global variable ColumnShown.
In my test, if the user changes a cell in column 0 to a value grater than 20 then “Clicks” on a cell in column 2 HideColumn, then the error does not pop up and the selection changes to the first cell in the previous row.
private void dataGridView1_SelectionChanged(object sender, EventArgs e) {
if (ColumnShown) {
if (ValueGreaterThan20) {
int curColIndex = dataGridView1.CurrentCell.ColumnIndex;
if (dataGridView1.Columns[curColIndex].Name == "HideColumn") {
dataGridView1.CurrentCell = dataGridView1.Rows[PreviousRow].Cells["ConditionColumn"];
dataGridView1.CurrentCell.Selected = true;
}
dataGridView1.Columns["HideColumn"].Visible = false;
ColumnShown = false;
}
}
else {
if (!ValueGreaterThan20) {
dataGridView1.Columns["HideColumn"].Visible = true;
ColumnShown = true;
}
}
}
As I said its hacky but it works with some caveats. Hope this helps!

Labels value not changing based on condition

I have a gridview in winforms which displays bill details. There is a column named total. I created a label with a text "0". Then I used a for loop to get the sum of total column in labels text property.
decimal tot1 = 0;
for (int i = 0; i < dgv_SaleReport.RowCount - 1; i++)
{
tot1 += Convert.ToDecimal(dgv_SaleReport.Rows[i].Cells[7].Value);
}
label_Total1SaleReport.Text = tot1.ToString();
But this code is not working. I added same code in other menu items datagridview and it's working. Right now, I have a tabcontrol which has 4 tabs. In all these 4 tabs dgv the code is not working. Can't just figure it out.

WPF Dynamically Merge and Split Grid Cells

This question might be a bit open ended but I am looking for advice on how to tackle the issue of merging and splitting Grid cells dynamically.
So currently I generate a Grid of X rows and Y columns and each of these cells is then populated with a StackPanel. When clicked a StackPanel has a border added to it and an array holding bool values representing each Grid cell is modified to show that it has been clicked. The grid holds no other controls. This is all done programmatically in c# rather than in the xaml.
What I want to be able to do is to merge and split selected cells in the grid if they are next to each other by hitting a button. I have found no way of easily doing this and I am wondering if anyone can recommend a good approach for this issue.
I am not tied to my current approach and any alternate method suggestions are appreciated.
I just posted one example of how to do this on Github.
The basic idea is to initialize the grid with the maximum number of rows and columns and then create and delete cells while modifying their positions and spans as necessary to fill the grid.
Here are the most relevant snippets:
Initialization: (MainWindow.xaml.cs)
public MainWindow()
{
InitializeComponent();
var numRows = grid.RowDefinitions.Count;
var numCols = grid.ColumnDefinitions.Count;
for (int i = 0; i < numRows; i++)
{
for (int j = 0; j < numCols; j++)
{
var item = new DynamicGridItem(j, i);
item.Merge += HandleMerge;
item.Split += HandleSplit;
grid.Children.Add(item);
Grid.SetRow(item, i);
Grid.SetColumn(item, j);
}
}
}
Here you can see that my cell items are a custom UserControl class with a constructor that takes two integers as X and Y grid coordinates. These coordinates can then be used to simplify grid row and column assignment. They aren't strictly necessary in my example, but could be helpful if storing/loading grid items and working with them in other code modules.
Merge Event:
My example uses a single Merge event with a boolean event argument to indicate whether the merge is to the left or to the right. The actual event handler just figures out whether to offset left or right and passes that information as well as the source item to the HandleMergeOffset method below.
private void HandleMergeOffset(DynamicGridItem item, int offset)
{
var otherItem = FindItemByXOffset(item, offset);
if (otherItem == null)
{
return; // Nothing to do
}
otherItem.Merge -= HandleMerge;
otherItem.Split -= HandleSplit;
Grid.SetColumnSpan(item, Grid.GetColumnSpan(item) + Grid.GetColumnSpan(otherItem));
grid.Children.Remove(otherItem);
if (offset < 0) // Reposition item if merging left
{
Grid.SetColumn(item, otherItem.X);
item.X = otherItem.X;
}
}
You could probably write a better FindItem method than I did. The basic idea is to find the neighbor to remove, expand the current item to include both original column spans, unhook events from the removed item, and of course remove it from the grid.
Split Event:
Since we remove an item on Merge, we create a new item on Split. I decided not to bother with a SplitLeft or SplitRight designation. This code will always create a minimum cell at the left-most index of the original cell and push the remainder one column right.
private void HandleSplit(object sender, EventArgs e)
{
var item = (DynamicGridItem)sender;
var itemColSpan = Grid.GetColumnSpan(item);
if (itemColSpan < 2)
{
return; // Nothing to do
}
var newItem = new DynamicGridItem(item.X, item.Y);
newItem.Merge += HandleMerge;
newItem.Split += HandleSplit;
grid.Children.Add(newItem);
Grid.SetColumn(newItem, newItem.X);
Grid.SetRow(newItem, newItem.Y);
Grid.SetColumn(item, item.X + 1);
Grid.SetColumnSpan(item, itemColSpan - 1);
item.X += 1;
}
Note that this makes no attempt to handle splitting a single cell into half cells (so that we never mutate the original grid or deal with sub-grids). If you need arbitrary dynamic grid behavior, you'll want to look elsewhere.

How to add dynamic control to datagridview?

I am little bit confused here i want some code here for this one or control
here is my requirement
I have datagridview like this
Now when i add 7 to total taka then another one will be shown like this one
now real scenario when i add value 3 to total taka then in second gridview it should be shown like this
Srno Meters
1 null
2 null
3 null
same should be repeated adding new rows to first datagridview how would i achieve this?
You can try adding code to a CellEndEdit event handler, then you can show an already created hidden DataGridView as the second one or you can also create that DataGridView on the fly. It's up to you. I prefer to show that DataGridView and initialize the number of rows. Here is the code helping you understand the idea:
//First you have to layout 2 DataGridViews at design time and set the Visible of the second
//DataGridView to false
//Your dataGridView2 should also have 2 columns added at design time as shown
//in your second picture.
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e){
//Suppose the column with header Total kaka has name TotalKaka
if (dataGridView1.Columns[e.ColumnIndex].Name == "TotalKaka") {
int i;
if (int.TryParse(dataGridView1[e.ColumnIndex, e.RowIndex].Value.ToString(), out i))
{
dataGridView2.Rows.Clear();
dataGridView2.Rows.Add(i);
for (int j = 0; j < i; j++)
dataGridView2[0, j].Value = j + 1;
dataGridView2.Show();
dataGridView2.CurrentCell = dataGridView2[1, 0];
dataGridView2.Focus();
}
}
}
//you should have some Submit button to submit the values entered into the second
//dataGridView, we should process something and surely hide the dataGridView2
private void submit_Click(object sender, EventArgs e){
dataGridView2.Hide();
//other code to process your data
//....
}
NOTE: This answers to your actual requirement in this question, I guess you may have more problems such as How to process the data entered in dataGridView2? How to show the dataGridView2 in another form? ... such problems do exist and I think you should ask for the solution in other questions, don't try asking for solving them right in this question.

Getting the ID of a GridView from a button Click contained within that gridview

I have a situation where I have Multiple GridViews on a page GridView1 - GridView4.
All these GridViews have TextBoxes in the cells, I would like to run a calculation function only on the Column of the TextBox that was Clicked and on that particular GridView.
Example: A user clicks on a TextBox in Column 2 of GridView3 and changes a number value, the OnTextChange event of that particular TextBox should fire to run a calculation and total all the numbers of GridView3 Col2 in a footer label.
I searched and all the other suggestions I found assume you have only 1 grid and that you know what preset Column the calculation is done at IE a prices column, But I don't know what Column info a user will edit in any of my 4 GridViews.
I gave up and resolved to just running the calculation code on the Entire GridView, not just the column edited, but to do this I need to Get the GridView.ID of the Gridview containing the TextBox. Extensive serches came up with how to get the ID of the sender Item but not the GridView.ID that contains the sender.
If anyone can help with either situation, please do so, I would really appreciate it.
As Per the conversations below, I have edited this post to include the Edited answer provided by Tim in the hopes that others may learn from this code:
//This is working code
protected void textBox_TextChanged(Object sender, EventArgs e)
{
TextBox txt = (TextBox)sender;
GridViewRow gvRow = (GridViewRow)txt.NamingContainer;
GridView Grid = (GridView)gvRow.NamingContainer;
// if you need the index of the column of the TextBox
int colIndex = 0;
for (int i = 0; i < gvRow.Cells.Count; i++)
{
if (gvRow.Cells[i] == txt.Parent)
{
colIndex = i;
Msg.Text = "Col: "+colIndex.ToString();
break;
}
}
int gridNum = 0;
switch (Grid.ID)
{
case "GridView1":
gridNum = 1;
break;
case "GridView2":
gridNum = 2;
break;
case "GridView3":
gridNum = 3;
break;
case "GridView4":
gridNum = 4;
break;
}
Label ColTotal = (Label)Grid.FooterRow.FindControl("G" + gridNum + "TotalPP" + colIndex);
double total = 0;
foreach (GridViewRow row in Grid.Rows)
{
TextBox tb = (TextBox)row.FindControl(txt.ID);
double d;
if (double.TryParse(tb.Text, out d))
total += d;
}
ColTotal.Text = total.ToString();
}
As you can see, according to the way the code is set up, the colIndex will help me find the footer label for each column and put it's total in.
You could let all TextBoxes handle the same TextChanged event and use this (untested) code:
protected void textBox_TextChanged(Object sender, EventArgs e)
{
TextBox txt = (TextBox) sender;
GridViewRow gvRow = (GridViewRow) txt.NamingContainer;
GridView gv = (GridView) gvRow.NamingContainer;
// if you need the index of the column of the TextBox
// as commented below you're using .NET 2.0
int colIndex;
for (int i = 0; i < gvRow.Cells.Count; i++)
{
if (gvRow.Cells[i] == txt.Parent)
{
colIndex = i;
break;
}
}
double total = 0;
foreach (GridViewRow row in gv.Rows)
{
TextBox tb = (TextBox)row.FindControl(txt.ID);
double d;
if (double.TryParse(tb.Text, out d))
total += d;
}
var tbFooter = (TextBox) gv.FooterRow.FindControl(txt.ID);
tbFooter.Text = total.ToString();
}
Of course assuming that all TextBoxes are in TemplateFields and the footer TextBox has the same ID. Remember to set AutoPostback to true if you want to postback immediately.
When TextChanged runs, sender is a reference to that textbox. You can use the parent property to find out what is the parent of that textbox. If you chain parents together (something like txt.Parent.Parent, you'll eventually find the GridViewRow, and GridView objects. Since you want to calculate the column, you'd have to find the cell that the control is in first, find the cell's index, then bubble up to the grid, loop through each row in that grid, and grab the values in the matching index. I hope you don't have paging enabled, because that will skew the numbers (since they aren't all present). Not an easy task, but possible.
From a performance perspective, you would get better performance with a client-side solution using JQuery or something else.

Categories

Resources