Textbox input range of numbers - vb.net

I'm trying to handle my textbox input values. I want the user to only be able to input numbers within a range using KeyPress. Ex. (0 - 1000). I have the code to prevent any input thats not a number. I can't quite figure out how to prevent the user from inputting a value thats not within a certain range.
Private Sub txt2x6LumberQuanity_KeyPress(sender As Object, e As KeyPressEventArgs) Handles txt2x6LumberQuanity.KeyPress
If Not Char.IsNumber(e.KeyChar) And Not Char.IsControl(e.KeyChar) Then
e.Handled = True
End If
End Sub
Does anybody have any suggestions. I've spent a couple hours searching but can't seem to find the right solution.

I would use the text changed and ErrorProvider component for this effect:
Valid Entry
Invalid Entry
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public int User2x6LumberQuantity
{
get
{
int x;
if (int.TryParse(txt2x6LumberQuantity.Text, out x))
{
return x;
}
return 0;
}
}
private void txt2x6LumberQuantity_TextChanged(object sender, EventArgs e)
{
errorProvider1.SetError(txt2x6LumberQuantity, null);
int x=User2x6LumberQuantity;
if (x<0||x>1000)
{
errorProvider1.SetError(txt2x6LumberQuantity, "Value Must Be (0-1000)");
continueButton.Enabled=false;
}
else
{
continueButton.Enabled=true;
}
}
}

You could add this to the Keypress event handler
If Char.IsNumber(e.KeyChar) Then
Dim newtext As String = TextBox1.Text.Insert(TextBox1.SelectionStart, e.KeyChar.ToString)
If Not IsNumeric(newtext) OrElse CInt(newtext) > 1000 OrElse CInt(newtext) < 0 Then e.Handled = True
End If

Related

Adding quantity if the product is exist in the DataGridView

Can someone help me with my problem?
The picture below shows the same item I inputted. What I want is I don't like to show duplicate items in DataGridView. If the same product record adds, then the new will not be showed, it just add the quantity when clicking the "Save" button. And I don't know how to code it I'm just new to vb.net. Can somebody help me how to do it?? It would be a big help for me if you do, thank you so much!
Below is my code for Save button:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
initializeCon()
Dim found As Boolean = False
If (DataGridView1.Rows.Count > 0) Then
For Each row As DataGridViewRow In DataGridView1.Rows
If (Convert.ToString(row.Cells(1).Value) = dtDate.Text) And (Convert.ToString(row.Cells(2).Value) = txtProductCode.Text) AndAlso
(Convert.ToString(row.Cells(3).Value) = txtProductName.Text) Then
row.Cells(4).Value = Convert.ToString(txtQuantity.Text + Convert.ToInt16(row.Cells(4).Value))
found = True
End If
Next
If Not found Then
cmd = New SqlCommand("INSERT INTO tbl_productOrders VALUES('" & txtID.Text & "','" & dtDate.Text & "','" & txtProductCode.Text & "','" & txtProductName.Text & "'," & txtQuantity.Text & ");", con)
cmd.ExecuteNonQuery()
clrtxt()
SaveMsg()
Getdata()
End If
End If
End Sub
this is an axample to avoid duplicates while adding rows
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim exist As Boolean = False, numrow As Integer = 0, numtext As Integer
For Each itm As DataGridViewRow In DataGridView1.Rows
If itm.Cells(0).Value IsNot Nothing Then
If itm.Cells(0).Value.ToString = TextBox1.Text Then
exist = True
numrow = itm.Index
numtext = CInt(itm.Cells(1).Value)
Exit For
End If
End If
Next
If exist = False Then
DataGridView1.Rows.Add(New String() {TextBox1.Text, TextBox2.Text})
Else
DataGridView1.Rows(numrow).Cells(1).Value = CInt(TextBox2.Text) + numtext
End If
End Sub
There are two aspects to improve your program.
1. Make comparing products better
Since you need to check individual properties to find equal product, the better way is to overload == operator along with implementing IEquatable interface. You can read more here. When you do this, you can compare products with == operator: if (product1 == product2) { }. In this case three properties are being compared. If they all are the same, then two products are equal.
2. Make adding products to DataGridView easier
In order to make adding products to DataGridView easier, you need to leverage the handy binding mechanism in Windows Forms, which appeared in .NET Framework 2.0 - BindingSource. It acts like a medium between your data and controls. In order to make this class usable, you need to use another handy class - BindingList. You feed BindingList to BindingSource and assign BindingSource to DataSource property of DataGridView. After that you work only with BindingList collection (adding/removing/editing) - and all the propagation is done by BindinSource.
To summarize, here is the explanation and full code. Here you can download the project itself.
When you add product, the code first checks whether such product already exists in DataGridView (Add product button). Do note that we don't work with DataGridView directly - we work only with the collection. Thanks to enhancements, we can use LINQ here. If the product is not found, we add it to collection. If it's found, we just update the quantity.
If you need to update selected product data (Change quantity button), you only need to retrieve it from selected rows and use BoundDataItem - this is where our product is located. Then just update properties of product you need.
If you need to remove product (Delete product button), retrieve it from selected row and delete it from the collection.
Important! Do not forget to call Refresh() method on DataGridView after all taken actions in order to see the changes.
namespace WinFormsApp
{
public partial class Root : Form
{
private BindingList<Product> list = null;
private BindingSource bindingSource = null;
public Root() => InitializeComponent();
private void OnFormLoaded(object sender, EventArgs e)
{
// Our collection holding products
list = new BindingList<Product>();
// I've set up columns manually (with applied binding),
// so switch generating columns off.
dataGrid.AutoGenerateColumns = false;
// Disable adding new row
dataGrid.AllowUserToAddRows = false;
// Create our medium between grid and collection
bindingSource = new BindingSource { DataSource = list };
// Set binding
dataGrid.DataSource = bindingSource;
}
private void OnAddRecord(object sender, EventArgs e)
{
// Create new product
var new_product = new Product
{
Date = dtPicker.Value.ToShortDateString(),
Code = txtCode.Text,
Name = txtName.Text,
Quantity = npQuantity.Value
};
// No need to check individual properties here
// as == and IEquatable will do all the work.
// We can safely use LINQ here.
var p = list.FirstOrDefault(x => x == new_product);
if (p == null)
{
// Product is not found. Add it to list.
list.Add(new_product);
}
else
{
// Product is found. Update quantity.
p.Quantity += new_product.Quantity;
}
dataGrid.Refresh(); //Required to reflect changes
}
private void OnChangeQuantity(object sender, EventArgs e)
{
// Change quantity here.
var product = GetProduct();
if (product != null)
{
// Update product's quantity.
product.Quantity *= 2;
// Do not forget to refresh grid.
dataGrid.Refresh();
}
}
private void OnDeleteProduct(object sender, EventArgs e)
{
// Delete product here.
var product = GetProduct();
if (product != null)
{
// We need to delete product only from collection
list.Remove(product);
// Do not forget to refresh grid
dataGrid.Refresh();
}
}
// Retrieve product from selected row.
private Product GetProduct() =>
dataGrid.SelectedCells.Count == 0 ?
null :
dataGrid.SelectedCells[0].OwningRow.DataBoundItem as Product;
}
class Product : IEquatable<Product>
{
public string Date { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public decimal Quantity { get; set; }
// Overload == operator
public static bool operator ==(Product firstProduct, Product secondProduct)
{
// Check for null on left side.
if (Object.ReferenceEquals(firstProduct, null))
{
if (Object.ReferenceEquals(secondProduct, null))
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return firstProduct.Equals(secondProduct);
}
// Overload != operator (required when overloading == operator)
public static bool operator !=(Product firstProduct, Product secondProduct) =>
!(firstProduct == secondProduct);
// Implementing IEquatable<T> interface
public bool Equals(Product other)
{
// If 'other' is null, return false.
if (Object.ReferenceEquals(other, null))
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, other))
{
return true;
}
// If run-time types are not exactly the same, return false.
if (this.GetType() != other.GetType())
{
return false;
}
// Return true if the fields match.
return
Date == other.Date &&
Code == other.Code &&
Name == other.Name;
}
public override bool Equals(object obj) => this.Equals(obj as Product);
public override int GetHashCode() =>
Date.GetHashCode() + Code.GetHashCode() + Name.GetHashCode();
// Optional. For debugging purposes.
public override string ToString() =>
$"Date: {Date}, Code: {Code}, Name: {Name}, Quantity: {Quantity}";
}
}

Incorrect value using LINQ on DataGridView [duplicate]

I have a winforms app and want to trigger some code when a checkbox embedded in a DataGridView control is checked / unchecked. Every event I have tried either
Triggers as soon as the CheckBox is clicked but before its checked state changes, or
Triggers only once the CheckBox looses its focus
I can't seem to find event that triggers immediately after the checked state changes.
Edit:
What I am trying to achieve is that when the checked state of a CheckBox in one DataGridView changes, the data in two other DataGridViews changes. Yet all the events I have used, the data in the other grids only changes after the CheckBox in the first DataGridView looses focus.
To handle the DatGridViews CheckedChanged event you must first get the CellContentClick to fire (which does not have the CheckBoxes current state!) then call CommitEdit. This will in turn fire the CellValueChanged event which you can use to do your work. This is an oversight by Microsoft. Do some thing like the following...
private void dataGridViewSites_CellContentClick(object sender,
DataGridViewCellEventArgs e)
{
dataGridViewSites.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
/// <summary>
/// Works with the above.
/// </summary>
private void dataGridViewSites_CellValueChanged(object sender,
DataGridViewCellEventArgs e)
{
UpdateDataGridViewSite();
}
P.S. Check this article https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.currentcelldirtystatechanged(v=vs.110).aspx
I found #Killercam's solution to work but was a bit dodgy if the user double clicked too fast. Not sure if other's found that the case either. I found a another solution here.
It uses the datagrid's CellValueChanged and CellMouseUp. Changhong explains that
"The reason for that is OnCellvalueChanged event won’t fire until the DataGridView thinks you have completed editing. This makes senses for a TextBox Column, as OnCellvalueChanged wouldn’t [bother] to fire for each key strike, but it doesn’t [make sense] for a CheckBox."
Here it is in action from his example:
private void myDataGrid_OnCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
// Handle checkbox state change here
}
}
And the code to tell the checkbox it is done editing when it is clicked, instead of waiting till the user leaves the field:
private void myDataGrid_OnCellMouseUp(object sender,DataGridViewCellMouseEventArgs e)
{
// End of edition on each click on column of checkbox
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
myDataGrid.EndEdit();
}
}
Edit: A DoubleClick event is treated separate from a MouseUp event. If a DoubleClick event is detected, the application will ignore the first MouseUp event entirely. This logic needs to be added to the CellDoubleClick event in addition to the MouseUp event:
private void myDataGrid_OnCellDoubleClick(object sender,DataGridViewCellEventArgs e)
{
// End of edition on each click on column of checkbox
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
myDataGrid.EndEdit();
}
}
jsturtevants's solution worked great. However, I opted to do the processing in the EndEdit event. I prefer this approach (in my application) because, unlike the CellValueChanged event, the EndEdit event does not fire while you are populating the grid.
Here is my code (part of which is stolen from jsturtevant:
private void gridCategories_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
{
//do some stuff
}
}
private void gridCategories_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
{
gridCategories.EndEdit();
}
}
Here is some code:
private void dgvStandingOrder_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (dgvStandingOrder.Columns[e.ColumnIndex].Name == "IsSelected" && dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
{
bool isChecked = (bool)dgvStandingOrder[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if (isChecked == false)
{
dgvStandingOrder.Rows[e.RowIndex].Cells["Status"].Value = "";
}
dgvStandingOrder.EndEdit();
}
}
private void dgvStandingOrder_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
{
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
following Killercam'answer, My code
private void dgvProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
dgvProducts.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
and :
private void dgvProducts_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (dgvProducts.DataSource != null)
{
if (dgvProducts.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString() == "True")
{
//do something
}
else
{
//do something
}
}
}
This also handles the keyboard activation.
private void dgvApps_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if(dgvApps.CurrentCell.GetType() == typeof(DataGridViewCheckBoxCell))
{
if (dgvApps.CurrentCell.IsInEditMode)
{
if (dgvApps.IsCurrentCellDirty)
{
dgvApps.EndEdit();
}
}
}
}
private void dgvApps_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// handle value changed.....
}
Ben Voigt found the best solution in a comment-reply above:
private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Seriously, that's ALL you need.
What worked for me was CurrentCellDirtyStateChanged in combination with datagridView1.EndEdit()
private void dataGridView1_CurrentCellDirtyStateChanged( object sender, EventArgs e ) {
if ( dataGridView1.CurrentCell is DataGridViewCheckBoxCell ) {
DataGridViewCheckBoxCell cb = (DataGridViewCheckBoxCell)dataGridView1.CurrentCell;
if ( (byte)cb.Value == 1 ) {
dataGridView1.CurrentRow.Cells["time_loadedCol"].Value = DateTime.Now.ToString();
}
}
dataGridView1.EndEdit();
}
It's all about editing the cell, the problem that is the cell didn't edited actually, so you need to save The changes of the cell or the row to get the event when you click the check box so you can use this function:
datagridview.CommitEdit(DataGridViewDataErrorContexts.CurrentCellChange)
with this you can use it even with a different event.
I have found a simpler answer to this problem. I simply use reverse logic. The code is in VB but it is not much different than C#.
Private Sub DataGridView1_CellContentClick(sender As Object, e As
DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
Dim _ColumnIndex As Integer = e.ColumnIndex
Dim _RowIndex As Integer = e.RowIndex
'Uses reverse logic for current cell because checkbox checked occures
'after click
'If you know current state is False then logic dictates that a click
'event will set it true
'With these 2 check boxes only one can be true while both can be off
If DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False And
DataGridView1.Rows(_RowIndex).Cells("Column3").Value = True Then
DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False
End If
If DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False And
DataGridView1.Rows(_RowIndex).Cells("Column2").Value = True Then
DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False
End If
End Sub
One of the best things about this is no need for multiple events.
I've tried some answers from here, but I've always had some kind of problem (like double clicking or using the keyboard). So, I combined some of them and got a consistent behavior (it's not perfect, but works properly).
void gridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
if(gridView.CurrentCell.GetType() != typeof(DataGridViewCheckBoxCell))
return;
if(!gridView.CurrentCell.IsInEditMode)
return;
if(!gridView.IsCurrentCellDirty)
return;
gridView.EndEdit();
}
void gridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e) {
if(e.ColumnIndex == gridView.Columns["cFlag"].Index && e.RowIndex >= 0)
gridView.EndEdit();
}
void gridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if(e.ColumnIndex != gridView.Columns["cFlag"].Index || e.RowIndex < 0)
return;
// Do your stuff here.
}
The Code will loop in DataGridView and Will check if CheckBox Column is Checked
private void dgv1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1)
{
dgv1.CommitEdit(DataGridViewDataErrorContexts.Commit);
var i = 0;
foreach (DataGridViewRow row in dgv1.Rows)
{
if (Convert.ToBoolean(row.Cells[0].Value))
{
i++;
}
}
//Enable Button1 if Checkbox is Checked
if (i > 0)
{
Button1.Enabled = true;
}
else
{
Button1.Enabled = false;
}
}
}
In the event CellContentClick you can use this strategy:
private void myDataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 2)//set your checkbox column index instead of 2
{ //When you check
if (Convert.ToBoolean(myDataGrid.Rows[e.RowIndex].Cells[2].EditedFormattedValue) == true)
{
//EXAMPLE OF OTHER CODE
myDataGrid.Rows[e.RowIndex].Cells[5].Value = DateTime.Now.ToShortDateString();
//SET BY CODE THE CHECK BOX
myDataGrid.Rows[e.RowIndex].Cells[2].Value = 1;
}
else //When you decheck
{
myDataGrid.Rows[e.RowIndex].Cells[5].Value = String.Empty;
//SET BY CODE THE CHECK BOX
myDataGrid.Rows[e.RowIndex].Cells[2].Value = 0;
}
}
}
The best way that I found (which also doesn't use multiple events) is by handling the CurrentCellDirtyStateChanged event.
private void dataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dataGridMatten.CurrentCell.OwningColumn == dataGridMatten.Columns["checkBoxColumn"] && dataGridMatten.IsCurrentCellDirty)
{
dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
//your code goes here
}
}
To do this when using the devexpress xtragrid, it is necessary to handle the EditValueChanged event of a corresponding repository item as described here. It is also important to call the gridView1.PostEditor() method to ensure the changed value has been posted. Here is an implementation:
private void RepositoryItemCheckEdit1_EditValueChanged(object sender, System.EventArgs e)
{
gridView3.PostEditor();
var isNoneOfTheAboveChecked = false;
for (int i = 0; i < gridView3.DataRowCount; i++)
{
if ((bool) (gridView3.GetRowCellValue(i, "NoneOfTheAbove")) && (bool) (gridView3.GetRowCellValue(i, "Answer")))
{
isNoneOfTheAboveChecked = true;
break;
}
}
if (isNoneOfTheAboveChecked)
{
for (int i = 0; i < gridView3.DataRowCount; i++)
{
if (!((bool)(gridView3.GetRowCellValue(i, "NoneOfTheAbove"))))
{
gridView3.SetRowCellValue(i, "Answer", false);
}
}
}
}
Note that because the xtragrid doesnt provide an enumerator it is necessary to use a for loop to iterate over rows.
Removing the focus after the cell value changes allow the values to update in the DataGridView. Remove the focus by setting the CurrentCell to null.
private void DataGridView1OnCellValueChanged(object sender, DataGridViewCellEventArgs dataGridViewCellEventArgs)
{
// Remove focus
dataGridView1.CurrentCell = null;
// Put in updates
Update();
}
private void DataGridView1OnCurrentCellDirtyStateChanged(object sender, EventArgs eventArgs)
{
if (dataGridView1.IsCurrentCellDirty)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
You can force the cell to commit the value as soon as you click the checkbox and then catch the CellValueChanged event. The CurrentCellDirtyStateChanged fires as soon as you click the checkbox.
The following code works for me:
private void grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
SendKeys.Send("{tab}");
}
You can then insert your code in the CellValueChanged event.
I use DataGridView with VirtualMode=true and only this option worked for me
(when both the mouse and the space bar are working, including repeated space clicks):
private void doublesGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var data_grid = (DataGridView)sender;
if (data_grid.CurrentCell.IsInEditMode && data_grid.IsCurrentCellDirty) {
data_grid.EndEdit();
}
}
private void doublesGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == CHECKED_COLUMN_NUM && e.RowIndex >= 0 && e.RowIndex < view_objects.Count) { // view_objects - pseudocode
view_objects[e.RowIndex].marked = !view_objects[e.RowIndex].marked; // Invert the state of the displayed object
}
}
this worked for me
private void employeeDataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index)
{
bool isChecked = (bool)employeeDataGridView.CurrentCell.Value;
if (isChecked)
{
MessageBox.Show("Checked " + isChecked); //out true;
}
else
{
MessageBox.Show("unChecked " + isChecked);
}
}
}
private void employeeDataGridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (employeeDataGridView.DataSource != null)
{
if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index && e.RowIndex != -1)
{
employeeDataGridView.EndEdit();
}
}
}
private void dataGridViewPendingBill_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
bool isChecked = (bool) dataGridViewPendingBill[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if (isChecked)
{
totalAmount += int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
textBoxAmount.Text = totalAmount.ToString();
}
else
{
totalAmount -= int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
textBoxAmount.Text = totalAmount.ToString();
}
dataGridViewPendingBill.EndEdit();
}

How to make a custom ComboBox (OwnerDrawFixed) looks 3D like the standard ComboBox?

I am making a custom ComboBox, inherited from Winforms' standard ComboBox. For my custom ComboBox, I set DrawMode to OwnerDrawFixed and DropDownStyle to DropDownList. Then I write my own OnDrawItem method. But I ended up like this:
How do I make my Custom ComboBox to look like the Standard one?
Update 1: ButtonRenderer
After searching all around, I found the ButtonRenderer class. It provides a DrawButton static/shared method which -- as the name implies -- draws the proper 3D button. I'm experimenting with it now.
Update 2: What overwrites my control?
I tried using the Graphics properties of various objects I can think of, but I always fail. Finally, I tried the Graphics of the form, and apparently something is overwriting my button.
Here's the code:
Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
Dim TextToDraw As String = _DefaultText
__Brush_Window.Color = Color.FromKnownColor(KnownColor.Window)
__Brush_Disabled.Color = Color.FromKnownColor(KnownColor.GrayText)
__Brush_Enabled.Color = Color.FromKnownColor(KnownColor.WindowText)
If e.Index >= 0 Then
TextToDraw = _DataSource.ItemText(e.Index)
End If
If TextToDraw.StartsWith("---") Then TextToDraw = StrDup(3, ChrW(&H2500)) ' U+2500 is "Box Drawing Light Horizontal"
If (e.State And DrawItemState.ComboBoxEdit) > 0 Then
'ButtonRenderer.DrawButton(e.Graphics, e.Bounds, VisualStyles.PushButtonState.Default)
Else
e.DrawBackground()
End If
With e
If _IsEnabled(.Index) Then
.Graphics.DrawString(TextToDraw, Me.Font, __Brush_Enabled, .Bounds.X, .Bounds.Y)
Else
'.Graphics.FillRectangle(__Brush_Window, .Bounds)
.Graphics.DrawString(TextToDraw, Me.Font, __Brush_Disabled, .Bounds.X, .Bounds.Y)
End If
End With
TextToDraw = Nothing
ButtonRenderer.DrawButton(Me.Parent.CreateGraphics, Me.ClientRectangle, VisualStyles.PushButtonState.Default)
'MyBase.OnDrawItem(e)
End Sub
And here's the result:
Replacing Me.Parent.CreateGraphics with e.Graphics got me this:
And doing the above + replacing Me.ClientRectangle with e.Bounds got me this:
Can anyone point me whose Graphics I must use for the ButtonRenderer.DrawButton method?
PS: The bluish border is due to my using PushButtonState.Default instead of PushButtonState.Normal
I Found An Answer! (see below)
I forgot where I found the answer... I'll edit this answer when I remember.
But apparently, I need to set the Systems.Windows.Forms.ControlStyles flags. Especially the ControlStyles.UserPaint flag.
So, my New() now looks like this:
Private _ButtonArea as New Rectangle
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
MyBase.SetStyle(ControlStyles.Opaque Or ControlStyles.UserPaint, True)
MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
MyBase.DropDownStyle = ComboBoxStyle.DropDownList
' Cache the button's modified ClientRectangle (see Note)
With _ButtonArea
.X = Me.ClientRectangle.X - 1
.Y = Me.ClientRectangle.Y - 1
.Width = Me.ClientRectangle.Width + 2
.Height = Me.ClientRectangle.Height + 2
End With
End Sub
And now I can hook into the OnPaint event:
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
If Me.DroppedDown Then
ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Pressed)
Else
ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Normal)
End If
MyBase.OnPaint(e)
End Sub
Note: Yes, the _ButtonArea rectangle must be enlarged by 1 pixel to all directions (up, down, left, right), or else there will be a 1-pixel 'perimeter' around the ButtonRenderer that shows garbage. Made me crazy for awhile until I read that I must enlarge the Control's rect for ButtonRenderer.
I had this problem myself and the reply by pepoluan got me started. I still think a few things are missing in order to get a ComboBox with looks and behavior similar to the standard ComboBox with DropDownStyle=DropDownList though.
DropDownArrow
We also need to draw the DropDownArrow. I played around with the ComboBoxRenderer, but it draws a dark border around the area of the drop down arrow so that didn't work.
My final solution was to simply draw a similar arrow and render it onto the button in the OnPaint method.
Hot Item Behavior
We also need to ensure our ComboBox has a hot item behavior similar to the standard ComboBox. I don't know of any simple and reliable method to know when a mouse is no longer above the control. Therefore I suggest using a Timer that checks at each tick whether the mouse is still over the control.
Edit
Just added a KeyUp event handler to make sure the control would update correctly when a selection was made using the keyboard. Also made a minor correction of where the text was rendered, to ensure it is more similar to the vanilla combobox' text positioning.
Below is the full code of my customized ComboBox. It allows you to display images on each item and is always rendered as in the DropDownList style, but hopefully it should be easy to accommodate the code to your own solution.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace CustomControls
{
/// <summary>
/// This is a special ComboBox that each item may conatins an image.
/// </summary>
public class ImageComboBox : ComboBox
{
private static readonly Size arrowSize = new Size(18, 20);
private bool itemIsHot;
/* Since properties such as SelectedIndex and SelectedItems may change when the mouser is hovering over items in the drop down list
* we need a property that will store the item that has been selected by comitted selection so we know what to draw as the selected item.*/
private object comittedSelection;
private readonly ImgHolder dropDownArrow = ImgHolder.Create(ImageComboBox.DropDownArrow());
private Timer hotItemTimer;
public Font SelectedItemFont { get; set; }
public Padding ImageMargin { get; set; }
//
// Summary:
// Gets or sets the path of the property to use as the image for the items
// in the System.Windows.Forms.ListControl.
//
// Returns:
// A System.String representing a single property name of the System.Windows.Forms.ListControl.DataSource
// property value, or a hierarchy of period-delimited property names that resolves
// to a property name of the final data-bound object. The default is an empty string
// ("").
//
// Exceptions:
// T:System.ArgumentException:
// The specified property path cannot be resolved through the object specified by
// the System.Windows.Forms.ListControl.DataSource property.
[DefaultValue("")]
[Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
public string ImageMember { get; set; }
public ImageComboBox()
{
base.SetStyle(ControlStyles.Opaque | ControlStyles.UserPaint, true);
//All the elements in the control are drawn manually.
base.DrawMode = DrawMode.OwnerDrawFixed;
//Specifies that the list is displayed by clicking the down arrow and that the text portion is not editable.
//This means that the user cannot enter a new value.
//Only values already in the list can be selected.
this.DropDownStyle = ComboBoxStyle.DropDownList;
//using DrawItem event we need to draw item
this.DrawItem += this.ComboBoxDrawItemEvent;
this.hotItemTimer = new Timer();
this.hotItemTimer.Interval = 250;
this.hotItemTimer.Tick += this.HotItemTimer_Tick;
this.MouseEnter += this.ImageComboBox_MouseEnter;
this.KeyUp += this.ImageComboBox_KeyUp;
this.SelectedItemFont = this.Font;
this.ImageMargin = new Padding(4, 4, 5, 4);
this.SelectionChangeCommitted += this.ImageComboBox_SelectionChangeCommitted;
this.SelectedIndexChanged += this.ImageComboBox_SelectedIndexChanged;
}
private static Image DropDownArrow()
{
var arrow = new Bitmap(8, 4, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(arrow))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.FillPolygon(Brushes.Black, ImageComboBox.CreateArrowHeadPoints());
}
return arrow;
}
private static PointF[] CreateArrowHeadPoints()
{
return new PointF[4] { new PointF(0, 0), new PointF(7F, 0), new PointF(3.5F, 3.5F), new PointF(0, 0) };
}
private static void DrawComboBoxItem(Graphics g, string text, Image image, Rectangle itemArea, int itemHeight, int itemWidth, Padding imageMargin
, Brush brush, Font font)
{
if (image != null)
{
// recalculate margins so image is always approximately vertically centered
int extraImageMargin = itemHeight - image.Height;
int imageMarginTop = Math.Max(imageMargin.Top, extraImageMargin / 2);
int imageMarginBotttom = Math.Max(imageMargin.Bottom, extraImageMargin / 2);
g.DrawImage(image, itemArea.X + imageMargin.Left, itemArea.Y + imageMarginTop, itemHeight, itemHeight - (imageMarginBotttom
+ imageMarginTop));
}
const double TEXT_MARGIN_TOP_PROPORTION = 1.1;
const double TEXT_MARGIN_BOTTOM_PROPORTION = 2 - TEXT_MARGIN_TOP_PROPORTION;
int textMarginTop = (int)Math.Round((TEXT_MARGIN_TOP_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
int textMarginBottom = (int)Math.Round((TEXT_MARGIN_BOTTOM_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
//we need to draw the item as string because we made drawmode to ownervariable
g.DrawString(text, font, brush, new RectangleF(itemArea.X + itemHeight + imageMargin.Left + imageMargin.Right, itemArea.Y + textMarginTop
, itemWidth, itemHeight - textMarginBottom));
}
private string GetDistplayText(object item)
{
if (this.DisplayMember == string.Empty) { return item.ToString(); }
else
{
var display = item.GetType().GetProperty(this.DisplayMember).GetValue(item).ToString();
return display ?? item.ToString();
}
}
private Image GetImage(object item)
{
if (this.ImageMember == string.Empty) { return null; }
else { return item.GetType().GetProperty(this.ImageMember).GetValue(item) as Image; }
}
private void ImageComboBox_SelectionChangeCommitted(object sender, EventArgs e)
{
this.comittedSelection = this.Items[this.SelectedIndex];
}
private void HotItemTimer_Tick(object sender, EventArgs e)
{
if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) { this.TurnOffHotItem(); }
}
private void ImageComboBox_KeyUp(object sender, KeyEventArgs e)
{
this.Invalidate();
}
private void ImageComboBox_MouseEnter(object sender, EventArgs e)
{
this.TurnOnHotItem();
}
private void ImageComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (!this.DroppedDown)
{
if (this.SelectedIndex > -1) { this.comittedSelection = this.Items[this.SelectedIndex]; }
else { this.comittedSelection = null; }
}
}
private void TurnOnHotItem()
{
this.itemIsHot = true;
this.hotItemTimer.Enabled = true;
}
private void TurnOffHotItem()
{
this.itemIsHot = false;
this.hotItemTimer.Enabled = false;
this.Invalidate(this.ClientRectangle);
}
/// <summary>
/// Draws overridden items.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ComboBoxDrawItemEvent(object sender, DrawItemEventArgs e)
{
//Draw backgroud of the item
e.DrawBackground();
if (e.Index != -1)
{
Brush brush;
if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) { brush = Brushes.White; }
else { brush = Brushes.Black; }
object item = this.Items[e.Index];
ImageComboBox.DrawComboBoxItem(e.Graphics, this.GetDistplayText(item), this.GetImage(item), e.Bounds, this.ItemHeight, this.DropDownWidth
, new Padding(0, 1, 5, 1), brush, this.Font);
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// define the area of the control where we will write the text
var topTextRectangle = new Rectangle(e.ClipRectangle.X - 1, e.ClipRectangle.Y - 1, e.ClipRectangle.Width + 2, e.ClipRectangle.Height + 2);
using (var controlImage = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height, PixelFormat.Format32bppArgb))
{
using (Graphics ctrlG = Graphics.FromImage(controlImage))
{
/* Render the control. We use ButtonRenderer and not ComboBoxRenderer because we want the control to appear with the DropDownList style. */
if (this.DroppedDown) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Pressed); }
else if (this.itemIsHot) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Hot); }
else { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Normal); }
// Draw item, if any has been selected
if (this.comittedSelection != null)
{
ImageComboBox.DrawComboBoxItem(ctrlG, this.GetDistplayText(this.comittedSelection), this.GetImage(this.comittedSelection)
, topTextRectangle, this.Height, this.Width - ImageComboBox.arrowSize.Width, this.ImageMargin, Brushes.Black, this.SelectedItemFont);
}
/* Now we need to draw the arrow. If we use ComboBoxRenderer for this job, it will display a distinct border around the dropDownArrow and we don't want that. As an alternative we define the area where the arrow should be drawn, and then procede to draw it. */
var dropDownButtonArea = new RectangleF(topTextRectangle.X + topTextRectangle.Width - (ImageComboBox.arrowSize.Width
+ this.dropDownArrow.Image.Width) / 2.0F, topTextRectangle.Y + topTextRectangle.Height - (topTextRectangle.Height
+ this.dropDownArrow.Image.Height) / 2.0F, this.dropDownArrow.Image.Width, this.dropDownArrow.Image.Height);
ctrlG.DrawImage(this.dropDownArrow.Image, dropDownButtonArea);
}
if (this.Enabled) { e.Graphics.DrawImage(controlImage, 0, 0); }
else { ControlPaint.DrawImageDisabled(e.Graphics, controlImage, 0, 0, Color.Transparent); }
}
}
}
internal struct ImgHolder
{
internal Image Image
{
get
{
return this._image ?? new Bitmap(1, 1); ;
}
}
private Image _image;
internal ImgHolder(Bitmap data)
{
_image = data;
}
internal ImgHolder(Image data)
{
_image = data;
}
internal static ImgHolder Create(Image data)
{
return new ImgHolder(data);
}
internal static ImgHolder Create(Bitmap data)
{
return new ImgHolder(data);
}
}
}

Hiding and Showing TabPages in tabControl

I am trying to show or hide tabpages as per user choice. If user selects gender male then form for male in a tabpage "male" should be displayed and if user selects female then similar next form should be displayed in next tab "female"
I tried using
tabControl1.TabPages.Remove(...)
and
tabControl1.TabPages.Add(...)
It adds and removes the tabpages but doing so will loose my controls on tabpages too... i can't see them back. what's the problem here?
I think the answer is much easier.
To hide the tab you just can use the way you already tried or adressing the TabPage itself.
TabControl1.TabPages.Remove(TabPage1) 'Could be male
TabControl1.TabPages.Remove(TabPage2) 'Could be female
a.s.o.
Removing the TabPage does not destroy it and the controls on it.
To show the corresponding tab again just use the following code
TabControl1.TabPages.Insert(0, TabPage1) 'Show male
TabControl1.TabPages.Insert(1, TabPage2) 'Show female
You could remove the tab page from the TabControl.TabPages collection and store it in a list. For example:
private List<TabPage> hiddenPages = new List<TabPage>();
private void EnablePage(TabPage page, bool enable) {
if (enable) {
tabControl1.TabPages.Add(page);
hiddenPages.Remove(page);
}
else {
tabControl1.TabPages.Remove(page);
hiddenPages.Add(page);
}
}
protected override void OnFormClosed(FormClosedEventArgs e) {
foreach (var page in hiddenPages) page.Dispose();
base.OnFormClosed(e);
}
Improving on the good solution by Hans Passant I decided to write an extension method based on his solution and adding other stuff as well. I am surprised that even in .NET 4 this basic functionality hasn't been fixed.
Implemented it as an Extension Method that can be reused in a more transparent manner
The clean up method only cleans up the pages of the control being disposed/cleaned up.
Whenever possible the tab page is restored to its same position. This isn't always
possible if you hide/show several tab pages.
It does some error and parameter checking
To make it invisible it finds out its parent. When making visible it has to be given
because the Parent property is null when the tab page has been removed.
public static class TabPageExtensions
{
private struct TabPageData
{
internal int Index;
internal TabControl Parent;
internal TabPage Page;
internal TabPageData(int index, TabControl parent, TabPage page)
{
Index = index;
Parent = parent;
Page = page;
}
internal static string GetKey(TabControl tabCtrl, TabPage tabPage)
{
string key = "";
if (tabCtrl != null && tabPage != null)
{
key = String.Format("{0}:{1}", tabCtrl.Name, tabPage.Name);
}
return key;
}
}
private static Dictionary<string, TabPageData> hiddenPages = new Dictionary<string, TabPageData>();
public static void SetVisible(this TabPage page, TabControl parent)
{
if (parent != null && !parent.IsDisposed)
{
TabPageData tpinfo;
string key = TabPageData.GetKey(parent, page);
if (hiddenPages.ContainsKey(key))
{
tpinfo = hiddenPages[key];
if (tpinfo.Index < parent.TabPages.Count)
parent.TabPages.Insert(tpinfo.Index, tpinfo.Page); // add the page in the same position it had
else
parent.TabPages.Add(tpinfo.Page);
hiddenPages.Remove(key);
}
}
}
public static void SetInvisible(this TabPage page)
{
if (IsVisible(page))
{
TabControl tabCtrl = (TabControl)page.Parent;
TabPageData tpinfo;
tpinfo = new TabPageData(tabCtrl.TabPages.IndexOf(page), tabCtrl, page);
tabCtrl.TabPages.Remove(page);
hiddenPages.Add(TabPageData.GetKey(tabCtrl, page), tpinfo);
}
}
public static bool IsVisible(this TabPage page)
{
return page != null && page.Parent != null; // when Parent is null the tab page does not belong to any container
}
public static void CleanUpHiddenPages(this TabPage page)
{
foreach (TabPageData info in hiddenPages.Values)
{
if (info.Parent != null && info.Parent.Equals((TabControl)page.Parent))
info.Page.Dispose();
}
}
}
A different approach would be to have two tab controls, one visible, and one not. You can move the tabs from one to the other like this (vb.net):
If Me.chkShowTab1.Checked = True Then
Me.tabsShown.TabPages.Add(Me.tabsHidden.TabPages("Tab1"))
Me.tabsHidden.TabPages.RemoveByKey("Tab1")
Else
Me.tabsHidden.TabPages.Add(Me.tabsShown.TabPages("Tab1"))
Me.tabsShown.TabPages.RemoveByKey("Tab1")
End If
If the tab order is important, change the .Add method on tabsShown to .Insert and specify the ordinal position. One way to do that is to call a routine that returns the desired ordinal position.
I prefer to make the flat style appearance:
https://stackoverflow.com/a/25192153/5660876
tabControl1.Appearance = TabAppearance.FlatButtons;
tabControl1.ItemSize = new Size(0, 1);
tabControl1.SizeMode = TabSizeMode.Fixed;
But there is a pixel that is shown at every tabPage, so if you delete all the text of every tabpage, then the tabs become invisible perfectly at run-time.
foreach (TabPage tab in tabControl1.TabPages)
{
tab.Text = "";
}
After that i use a treeview, to change through the tabpages... clicking on the nodes.
you can always hide or show the tabpage.
'in VB
myTabControl.TabPages(9).Hide() 'to hide the tabpage that has index 9
myTabControl.TabPages(9).Show() 'to show the tabpage that has index 9
I have my sample code working but want to make it somewhat more better refrencing the tab from list:
Public Class Form1
Dim State1 As Integer = 1
Dim AllTabs As List(Of TabPage) = New List(Of TabPage)
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Check1(State1)
State1 = CInt(IIf(State1 = 1, 0, 1))
End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AllTabs.Add(TabControl1.TabPages("TabPage1"))
AllTabs.Add(TabControl1.TabPages("TabPage2"))
End Sub
Sub Check1(ByVal No As Integer)
If TabControl1.TabPages.ContainsKey("TabPage1") Then
TabControl1.TabPages.Remove(TabControl1.TabPages("TabPage1"))
End If
If TabControl1.TabPages.ContainsKey("TabPage2") Then
TabControl1.TabPages.Remove(TabControl1.TabPages("TabPage2"))
End If
TabControl1.TabPages.Add(AllTabs(No))
End Sub
End Class
public static Action<Func<TabPage, bool>> GetTabHider(this TabControl container) {
if (container == null) throw new ArgumentNullException("container");
var orderedCache = new List<TabPage>();
var orderedEnumerator = container.TabPages.GetEnumerator();
while (orderedEnumerator.MoveNext()) {
var current = orderedEnumerator.Current as TabPage;
if (current != null) {
orderedCache.Add(current);
}
}
return (Func<TabPage, bool> where) => {
if (where == null) throw new ArgumentNullException("where");
container.TabPages.Clear();
foreach (TabPage page in orderedCache) {
if (where(page)) {
container.TabPages.Add(page);
}
}
};
}
Used like this:
var showOnly = this.TabContainer1.GetTabHider();
showOnly((tab) => tab.Text != "tabPage1");
Original ordering is retained by retaining a reference to the anonymous function instance.
It looks easier for me to clear all TabPages add add those wished:
PropertyTabControl.TabPages.Clear();
PropertyTabControl.TabPages.Add(AspectTabPage);
PropertyTabControl.TabPages.Add(WerkstattTabPage);
or
PropertyTabControl.TabPages.Clear();
PropertyTabControl.TabPages.Add(TerminTabPage);
Someone merged the C# answer into this one so I have to post my answer here. I didn't love the other solutions so I made a helper class that will make it easier to hide / show your tabs while retaining the order of tabs.
/// <summary>
/// Memorizes the order of tabs upon creation to make hiding / showing tabs more
/// straightforward. Instead of interacting with the TabCollection, use this class
/// instead.
/// </summary>
public class TabPageHelper
{
private List<TabPage> _allTabs;
private TabControl.TabPageCollection _tabCollection;
public Dictionary<string, int> TabOrder { get; private set; }
public TabPageHelper( TabControl.TabPageCollection tabCollection )
{
_allTabs = new List<TabPage>();
TabOrder = new Dictionary<string, int>();
foreach ( TabPage tab in tabCollection )
{
_allTabs.Add( tab );
}
_tabCollection = tabCollection;
foreach ( int index in Enumerable.Range( 0, tabCollection.Count ) )
{
var tab = tabCollection[index];
TabOrder[tab.Name] = index;
}
}
public void ShowTabPage( string tabText )
{
TabPage page = _allTabs
.Where( t => string.Equals( t.Text, tabText, StringComparison.CurrentCultureIgnoreCase ) )
.First();
int tabPageOrder = TabOrder[page.Name];
if ( !_tabCollection.Contains( page ) )
{
_tabCollection.Insert( tabPageOrder, page );
}
}
public void HideTabPage( string tabText )
{
TabPage page = _allTabs
.Where( t => string.Equals( t.Text, tabText, StringComparison.CurrentCultureIgnoreCase ) )
.First();
int tabPageOrder = TabOrder[page.Name];
if ( _tabCollection.Contains( page ) )
{
_tabCollection.Remove( page );
}
}
}
To use the class, instantiate it in your form load method after initializing your components by passing in the tab control's TabPages property.
public Form1()
{
InitializeComponent();
_tabHelper = new TabPageHelper( tabControl1.TabPages );
}
All of your tab pages should exist on application load (ie: in the Design view) because the class will remember the order of the tab pages when hiding / showing. You can hide or show them selectively throughout your application like this:
_tabHelper.HideTabPage("Settings");
_tabHelper.ShowTabPage("Schedule");
I've been using the same approach of saving the hidden TabPages in a private list, but the problem is that when I want to show the TabPage again, they doesn't appears in the original position (order). So, finally, I wrote a class in VB to add the TabControl with two methods: HideTabPageByName and ShowTabPageByName. You can just call the methods passing the name (not the TabPage instance).
Public Class CS_Control_TabControl
Inherits System.Windows.Forms.TabControl
Private mTabPagesHidden As New Dictionary(Of String, System.Windows.Forms.TabPage)
Private mTabPagesOrder As List(Of String)
Public Sub HideTabPageByName(ByVal TabPageName As String)
If mTabPagesOrder Is Nothing Then
' The first time the Hide method is called, save the original order of the TabPages
mTabPagesOrder = New List(Of String)
For Each TabPageCurrent As TabPage In Me.TabPages
mTabPagesOrder.Add(TabPageCurrent.Name)
Next
End If
If Me.TabPages.ContainsKey(TabPageName) Then
Dim TabPageToHide As TabPage
' Get the TabPage object
TabPageToHide = TabPages(TabPageName)
' Add the TabPage to the internal List
mTabPagesHidden.Add(TabPageName, TabPageToHide)
' Remove the TabPage from the TabPages collection of the TabControl
Me.TabPages.Remove(TabPageToHide)
End If
End Sub
Public Sub ShowTabPageByName(ByVal TabPageName As String)
If mTabPagesHidden.ContainsKey(TabPageName) Then
Dim TabPageToShow As TabPage
' Get the TabPage object
TabPageToShow = mTabPagesHidden(TabPageName)
' Add the TabPage to the TabPages collection of the TabControl
Me.TabPages.Insert(GetTabPageInsertionPoint(TabPageName), TabPageToShow)
' Remove the TabPage from the internal List
mTabPagesHidden.Remove(TabPageName)
End If
End Sub
Private Function GetTabPageInsertionPoint(ByVal TabPageName As String) As Integer
Dim TabPageIndex As Integer
Dim TabPageCurrent As TabPage
Dim TabNameIndex As Integer
Dim TabNameCurrent As String
For TabPageIndex = 0 To Me.TabPages.Count - 1
TabPageCurrent = Me.TabPages(TabPageIndex)
For TabNameIndex = TabPageIndex To mTabPagesOrder.Count - 1
TabNameCurrent = mTabPagesOrder(TabNameIndex)
If TabNameCurrent = TabPageCurrent.Name Then
Exit For
End If
If TabNameCurrent = TabPageName Then
Return TabPageIndex
End If
Next
Next
Return TabPageIndex
End Function
Protected Overrides Sub Finalize()
mTabPagesHidden = Nothing
mTabPagesOrder = Nothing
MyBase.Finalize()
End Sub
End Class
Public Shared HiddenTabs As New List(Of TabPage)()
Public Shared Visibletabs As New List(Of TabPage)()
Public Shared Function ShowTab(tab_ As TabPage, show_tab As Boolean)
Select Case show_tab
Case True
If Visibletabs.Contains(tab_) = False Then Visibletabs.Add(tab_)
If HiddenTabs.Contains(tab_) = True Then HiddenTabs.Remove(tab_)
Case False
If HiddenTabs.Contains(tab_) = False Then HiddenTabs.Add(tab_)
If Visibletabs.Contains(tab_) = True Then Visibletabs.Remove(tab_)
End Select
For Each r In HiddenTabs
Try
Dim TC As TabControl = r.Parent
If TC.Contains(r) = True Then TC.TabPages.Remove(r)
Catch ex As Exception
End Try
Next
For Each a In Visibletabs
Try
Dim TC As TabControl = a.Parent
If TC.Contains(a) = False Then TC.TabPages.Add(a)
Catch ex As Exception
End Try
Next
End Function
And building upon the answer by Emile (and Slugster), I found it a bit easier to extend the TabControl (instead of the TabPages). In this way I can set pages invisible or visible with a single call, and also not have to worry about the null parent references for the invisible pages.
Example call:
MyTabControl.SetTabVisibilityExt( "tabTests", isDeveloper);
public static class WinFormExtensions
{
public static TabPage FindTabByNameExt( this TabControl tc, string tabName)
{
foreach (TabPage tab in tc.TabPages)
if (tab.Name == tabName)
return tab;
return null;
}
private struct TabPageData
{
internal int Index;
internal TabControl Parent;
internal TabPage Page;
internal TabPageData(int index, TabControl parent, TabPage page)
{
Index = index;
Parent = parent;
Page = page;
}
internal static string GetKey(TabControl tc, TabPage tabPage)
{
string key = "";
if (tc == null || tabPage == null)
return key;
key = $"{tc.Name}:{tabPage.Name}";
return key;
}
internal static string GetKey(TabControl tc, string tabName)
{
string key = "";
if (tc == null)
return key;
key = $"{tc.Name}:{tabName}";
return key;
}
}
private static Dictionary<string, TabPageData> hiddenPages = new Dictionary<string, TabPageData>();
public static void SetTabVisibleExt(this TabControl tc, string tabName)
{
if (tc == null || tc.IsDisposed)
return;
if (tc.IsTabVisibleExt(tabName))
return;
string key = TabPageData.GetKey(tc, tabName);
if (hiddenPages.ContainsKey(key))
{
TabPageData tpinfo = hiddenPages[key];
if (tpinfo.Index < tc.TabPages.Count)
tc.TabPages.Insert(tpinfo.Index, tpinfo.Page); // add the page in the same position it had
else
tc.TabPages.Add(tpinfo.Page);
hiddenPages.Remove(key);
return;
}
else
throw new ApplicationException($"TabControl={tc.Name} does not have Invisible TabPage={tabName}");
}
public static void SetTabInvisibleExt(this TabControl tc, string tabName)
{
if (tc == null || tc.IsDisposed)
return;
if (IsTabInvisibleExt(tc, tabName))
return;
TabPage page = tc.FindTabByNameExt(tabName);
if (page != null)
{
string key = TabPageData.GetKey(tc, page);
TabPageData tpInfo = new TabPageData(tc.TabPages.IndexOf(page), tc, page);
tc.TabPages.Remove(page);
hiddenPages.Add(key, tpInfo);
return;
}
else // Could not find the tab, and it isn't already invisible.
throw new ApplicationException($"TabControl={tc.Name} could not locate TabPage={tabName}");
}
// A convenience method to combine the SetTabInvisible and SetTabInvisible.
public static void SetTabVisibilityExt(this TabControl tc, string tabName, bool? isVisible)
{
if (isVisible == null)
return;
if (isVisible.Value)
tc.SetTabVisibleExt(tabName);
else
tc.SetTabInvisibleExt(tabName);
}
public static bool IsTabVisibleExt(this TabControl tc, string tabName)
{
TabPage page = tc.FindTabByNameExt(tabName);
return page != null;
}
public static bool IsTabInvisibleExt(this TabControl tc, string tabName)
{
string key = TabPageData.GetKey(tc, tabName);
return hiddenPages.ContainsKey(key);
}
public static void CleanUpHiddenPagesExt(this TabControl tc)
{
foreach (TabPageData info in hiddenPages.Values)
{
if (info.Parent != null && info.Parent.Equals((TabControl)tc))
info.Page.Dispose();
}
}
}
If you can afford to use the Tag element of the TabPage, you can use this extension methods
public static void HideByRemoval(this TabPage tp)
{
TabControl tc = tp.Parent as TabControl;
if (tc != null && tc.TabPages.Contains(tp))
{
// Store TabControl and Index
tp.Tag = new Tuple<TabControl, Int32>(tc, tc.TabPages.IndexOf(tp));
tc.TabPages.Remove(tp);
}
}
public static void ShowByInsertion(this TabPage tp)
{
Tuple<TabControl, Int32> tagObj = tp.Tag as Tuple<TabControl, Int32>;
if (tagObj?.Item1 != null)
{
// Restore TabControl and Index
tagObj.Item1.TabPages.Insert(tagObj.Item2, tp);
}
}
Adding and removing tab may be a bit less effective
May be this will help
To hide/show tab page => let tabPage1 of tabControl1
tapPage1.Parent = null; //to hide tabPage1 from tabControl1
tabPage1.Parent = tabControl1; //to show the tabPage1 in tabControl1
There are at least two ways to code a solution in software... Thanks for posting answers. Just wanted to update this with another version. A TabPage array is used to shadow the Tab Control. During the Load event, the TabPages in the TabControl are copied to the shadow array. Later, this shadow array is used as the source to copy the TabPages into the TabControl...and in the desired presentation order.
Private tabControl1tabPageShadow() As TabPage = Nothing
Private Sub Form2_DailyReportPackageViewer_Load(sender As Object, e As EventArgs) Handles Me.Load
LoadTabPageShadow()
End Sub
Private Sub LoadTabPageShadow()
ReDim tabControl1tabPageShadow(TabControl1.TabPages.Count - 1)
For Each tabPage In TabControl1.TabPages
tabControl1tabPageShadow(tabPage.TabIndex) = tabPage
Next
End Sub
Private Sub ViewAllReports(sender As Object, e As EventArgs) Handles Button8.Click
TabControl1.TabPages.Clear()
For Each tabPage In tabControl1tabPageShadow
TabControl1.TabPages.Add(tabPage)
Next
End Sub
Private Sub ViewOperationsReports(sender As Object, e As EventArgs) Handles Button10.Click
TabControl1.TabPages.Clear()
For tabCount As Integer = 0 To 9
For Each tabPage In tabControl1tabPageShadow
Select Case tabPage.Text
Case "Overview"
If tabCount = 0 Then TabControl1.TabPages.Add(tabPage)
Case "Production Days Under 110%"
If tabCount = 1 Then TabControl1.TabPages.Add(tabPage)
Case "Screening Status"
If tabCount = 2 Then TabControl1.TabPages.Add(tabPage)
Case "Rework Status"
If tabCount = 3 Then TabControl1.TabPages.Add(tabPage)
Case "Secondary by Machine"
If tabCount = 4 Then TabControl1.TabPages.Add(tabPage)
Case "Secondary Set Ups"
If tabCount = 5 Then TabControl1.TabPages.Add(tabPage)
Case "Secondary Run Times"
If tabCount = 6 Then TabControl1.TabPages.Add(tabPage)
Case "Primary Set Ups"
If tabCount = 7 Then TabControl1.TabPages.Add(tabPage)
Case "Variance"
If tabCount = 8 Then TabControl1.TabPages.Add(tabPage)
Case "Schedule Changes"
If tabCount = 9 Then TabControl1.TabPages.Add(tabPage)
End Select
Next
Next
Solutions provided by Antony and Suroj are the simplest one.
In Antony's solution, that if condition is must before adding the tabpage. Adding the same tabpage again will actually add the second tabpage and remove controls on both the tabpages.
I used following code in my app.
'Hide all the tabpages except Navigator during app startup.
For Each itemTab As TabPage In TabControl1.TabPages
If itemTab.Text <> "Navigator" Then
TabControl1.TabPages.Remove(itemTab)
End If
Next
'Show the tab on button click
If Not TabControl1.Controls.Contains(TabDataSource) Then
TabControl1.TabPages.Add(TabDataSource)
End If
TabControl1.SelectedTab = TabDataSource
Always codes should be simple and easy to perform tasks for fast performance and for good reliability.
To add a page to a TabControl, the following code is enough.
If Tabcontrol1.Controls.Contains(TabPage1) Then
else
Tabcontrol1.Controls.Add(TabPage1)
End If
To remove a page from a TabControl, the following code is enough.
If Tabcontrol1.Controls.Contains(TabPage1) Then
Tabcontrol1.Controls.Remove(TabPage1)
End If
I wish to thank Stackoverflow.com for providing sincere help to programmers.
TabPanel1.Visible = true; // Show Tabpage 1
TabPanel1.Visible = false; //Hide Tabpage 1
First copy the tab into a variable then use insert to bring it back.
TabPage tpresult = tabControl1.TabPages[0];
tabControl1.TabPages.RemoveAt(0);
tabControl1.TabPages.Insert(0, tpresult);
You Can Use the Following
tabcontainer.tabs(1).visible=true
1 is tabindex

DataGridView_CellValidating

I have a datagridview that I would like to validate using the cellvalidating event. however as the user doesnt navigate between cells or rows in the datagridview. just enters data in a cell in the datagridview and then clicks a save button the cellvalidating event doesnt get fired.
any help would be greatly appreciated.
Before you save data, you can call ValidateChildren on the form to force all controls to validate themselves. the method will return false if there was a validation error. You use it like this:
Private Sub SaveButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SaveButton.Click
If Me.ValidateChildren Then
...Save
End If
End Sub
It is obviously in the Win Forms environment.
Here is the answer to the problem.
I am accepting a datatable as my datasource for the gridview
DataTable dtSource = new DataTable();
#region Data Table Creation
dtSource.Columns.Add("NumericColumn1");
dtSource.Columns.Add("NumericColumn2");
dtSource.Columns.Add("NumericColumn3");
#endregion
#region Add Rows
dtSource.Rows.Add("1", "2", "3");
dtSource.Rows.Add("4", "5", "6");
dtSource.Rows.Add("7", "8", "9");
#endregion
dataGridView1.DataSource = dtSource;
My objective is to check if a user has entered anything in any of the cell apart from a numeric value, then upon clicking on the SAVE button an error message should be populated.
In the cell validating event , I have written the following
private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
dataGridView1.Rows[e.RowIndex].ErrorText = "";
if (!(IsNumeric(e.FormattedValue.ToString(), System.Globalization.NumberStyles.Integer)))
{
flag = !flag;
dataGridView1.Rows[e.RowIndex].ErrorText = "Only numeric values are accepted";
}
else
{
flag = true;
}
}
The IsNumeric function is as under
public bool IsNumeric(string Val, System.Globalization.NumberStyles NumberStyle)
{
Double result;
return Double.TryParse(Val, NumberStyle, System.Globalization.CultureInfo.CurrentCulture, out result);
}
And in the SAVE button event , I am checking the status of the validation
private void SAVE_Click(object sender, EventArgs e)
{
if (flag == true)
{
MessageBox.Show("Every thing is ok");
}
}
Hope this helps
private void dgv_CellLeave(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex > -1)
{
if (dgv[e.ColumnIndex, e.RowIndex].IsInEditMode)
{
dgv.EndEdit();
}
}
}