How can I sort a WinForms DataGridView on a CheckBox column? - vb.net

So I had a DataGridView with autogenerated columns, some of which were checkbox columns. When I clicked on the check box column's header, it didn't sort. I researched it and it turns out that Microsoft didn't include automatic sorting for checkbox columns... Which I think is absurd--how hard is it to sort checked / not checked?
How can you get a DataGridView to sort check box columns for you?
Here's what I came up with:

You could also simply do this:
DataGridView.Columns("ColumnOfChoice").SortMode = DataGridViewColumnSortMode.Automatic
Works in vb.net 4.0

You only need to add next lines to the code of the form (tested in VB.NET 2013)
Private Sub DataGridView1_ColumnAdded(sender As Object, e As System.Windows.Forms.DataGridViewColumnEventArgs) Handles DataGridView1.ColumnAdded
If e.Column.GetType Is GetType(DataGridViewCheckBoxColumn) Then
e.Column.SortMode = DataGridViewColumnSortMode.Automatic
End If
End Sub

First you need to hook into two events, the column added event and the column header click event:
AddHandler dg.ColumnAdded, AddressOf dgColumnAdded
AddHandler dg.ColumnHeaderMouseClick, AddressOf dgSortColumns
Then, enable programmatic sorting for each check box column:
Private Sub dgColumnAdded(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs)
If e.Column.GetType Is GetType(DataGridViewCheckBoxColumn) Then
e.Column.SortMode = DataGridViewColumnSortMode.Programmatic
End If
End Sub
Then, create a handler that will sort a checkbox column, but do nothing for columns that will handle their own sorting:
Private Sub dgSortColumns(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
Dim dg As DataGridView = sender
Dim c As DataGridViewColumn = dg.Columns(e.ColumnIndex)
If c.SortMode = DataGridViewColumnSortMode.Programmatic Then
If dg.SortedColumn IsNot Nothing _
AndAlso dg.SortedColumn.Name <> c.Name Then
dg.Sort(c, System.ComponentModel.ListSortDirection.Ascending)
Else
Select Case dg.SortOrder
Case Windows.Forms.SortOrder.None
dg.Sort(c, System.ComponentModel.ListSortDirection.Ascending)
Case Windows.Forms.SortOrder.Ascending
dg.Sort(c, System.ComponentModel.ListSortDirection.Descending)
Case Windows.Forms.SortOrder.Descending
dg.Sort(c, System.ComponentModel.ListSortDirection.Ascending)
End Select
End If
End If
End Sub
And there you go! Now was it really that hard, Microsoft? ;-)

I'm not sure about VB, but for C# in VS2012 in the designer you can also set the SortMode.
Right-click on the DataGridView and go to "Edit Columns".
There's a drop-down for SortMode with a choice of NotSortable, Automatic, and Programmatic.
It appears that the default for most columns is Automatic, but for checkboxes (boolean) columns the default is NotSortable.

I have created an extension method that you can reuse, you just need to use it during the form load event.
------ Be sure that your DataSource is sortable. ------
If you are binding the DataGridView to a simple List it WONT WORK, you need to use something else, I recommend you to use this SortableBindingList; You can pass directly your original List IEnumerable to the SortableBindingList's constructor.
Load:
private void frmWithTheDataGrid_Load(object sender, EventArgs e)
{
this.yourDataGridView.SortabilizeMe();
}
Then add this into a static class to use it as an ExtensionMethod..
public static void SortabilizeMe(this DataGridView dgv)
{
dgv.ColumnAdded+= delegate(object sender, DataGridViewColumnEventArgs args)
{
args.Column.SortMode = DataGridViewColumnSortMode.Programmatic;
};
dgv.ColumnHeaderMouseClick += delegate(object sender, DataGridViewCellMouseEventArgs args)
{
var col = dgv.Columns[args.ColumnIndex];
if (dgv.SortedColumn != null && dgv.SortedColumn.Name != col.Name)
{
dgv.Sort(row, ListSortDirection.Ascending);
}
else
{
switch (dgv.SortOrder)
{
case SortOrder.None:
dgv.Sort(col, ListSortDirection.Ascending);
break;
case SortOrder.Ascending:
dgv.Sort(col, ListSortDirection.Descending);
break;
case SortOrder.Descending:
dgv.Sort(col, ListSortDirection.Ascending);
break;
}
}
};
}
Then magic will happen :)

Related

How to prevent datagridview from selecting the first cell as currentcell on opening

I'm trying to find a way to prevent my datagridview from selecting the first cell as default cell. Right now I have code that turns the backcolor of the cells in my datagridview to red if negative numbers are in the cells on import. However this won't work properly in my first cell since its already highlighted by default on import. If anyone can find out how to turn the selecting of the cell off I would greatly appreciate it! :)
I know it must be something simple like DataGridView1.CurrentCell.Selected = False
Handle the DataBindingComplete event of the DataGridView as so:
private void myGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
myGrid.ClearSelection();
}
Try this, works for me. Put this code anywhere within your form code with the datagrid in it.
Private Sub YourDataGridName_DataBindingComplete(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DataGridViewBindingCompleteEventArgs) _
Handles YourDataGridName.DataBindingComplete
Dim DGV As DataGridView
DGV = CType(sender, DataGridView)
DGV.ClearSelection()
End Sub
(source)
try
datagridview.currentrow.selected = true
this would make the whole row selected so the code that change the curent back color wont be effected.
I had a code for creating focus but i forget it. to set the selection of the grid you need to cahnge the direction
This one works (and with differents selectionmode gridviews)
Public Function Grdvw_Cell_Unselect(ByVal Grdvw As DataGridView) As Boolean
' cancel all defaut selection in a datagridview
Grdvw_Cell_Unselect = False
Try
Grdvw.ClearSelection()
Grdvw.Item(0, 0).Selected = False
Grdvw_Cell_Unselect = True
Catch ex As Exception
End Try
End Function
then use as this:
Grdvw_Cell_Unselect(your_datagridview) ...
What worked for me was to clear the selection and then set the row based on the row index. Hope this helps.
GridView.ClearSelection()
GridView.Rows(RowIndex).Selected = True
GridView.DataSource = DataTable
GridView.Refresh()
Bind event for Paint for datagridview.
Click datagridview > Properties > Paint > double click on space near Paint > It will create method similar to this: datagridview1_Paint(object sender, PaintEventArgs e)
Write these two lines in that method as displayed:enter code here
private void datagridview1_Paint(object sender, PaintEventArgs e) {
this.datagridview1.ClearSelection();
this.datagridview1.CurrentCell = null;
}

Radiobuttons in different group boxes

Quite a silly question, but still annoying.
The thing is that i have two group boxes where the titles have radiobuttons covering the group box titles.
Something like
(x) I want pizza
*Pizza stuff*
( ) I want Hamburger
*Hamburger stuff*
Since they're now in different group boxes, they can both be selected.
Is there a way to set/force the radiobuttons to be in the same "group"? Like in HTML where you set
name="WhatToEat" value="Pizza" for the first value and then
name="WhatToEat" value="Hamburger"
Or can i set the title for the groupbox to behave like a radio button or something?
Of course i can have the radio button outside the grop boxes, but i think having the titles as radiobuttons just makes the most sense and looks nicer.
If you have all your RadioButtons on a form. You can use a RadioButton variable to mark what is currently checked. Every time user checks a RadioButton, if it's not the currently checked RadioButton, make the currently checked RadioButton unchecked, and set the currently checked RadioButton to that RadioButton.
Here is my code:
public Form1(){
InitializeComponents();
currentChecked = radioButton1;
}
//Suppose the initially checked radio is radioButton1
RadioButton currentChecked;
//This is the CheckedChanged event handler used for all the radiobuttons
private void radioButtonChecked(object sender, EventArgs e)
{
RadioButton r = (RadioButton)sender;
if (r != currentChecked)
{
currentChecked.Checked = false;
currentChecked = r;
}
}
My code is much simpler without using any loop. It costs the additional currentChecked but it's not much.
Hope it helps!
No, it have to in one group ..
But for that case you can control in checked_change event
Private Sub RadioButton2_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton2.CheckedChanged
RadioButton1.Checked = Not RadioButton2.Checked
End Sub
Private Sub RadioButton1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged
RadioButton2.Checked = Not RadioButton1.Checked
End Sub
Just move them to the Form at run-time. Use PointToScreen() and PointToClient() to keep them in the same position as you placed them at desing-time. So you'd replace "RadioButton1", "RadioButton2", and "RadioButton3" with your title RadioButtons:
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim RadioTitles() As RadioButton = {RadioButton1, RadioButton2, RadioButton3}
For Each rb As RadioButton In RadioTitles
Dim pt As Point = Me.PointToClient(rb.PointToScreen(New Point(0, 0)))
Me.Controls.Add(rb)
rb.Location = pt
rb.BringToFront()
Next
End Sub
*You could put a value into the Tag() property of each RadioButton that should be the "title" and then search for those instead of hard-coding them into the array. Or maybe you could name them a certain way.
EDIT: You could make the "title" RadioButtons enable/disable their associated GroupBoxes when they are checked like this:
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim RadioTitles() As RadioButton = {RadioButton1, RadioButton2, RadioButton3}
For Each rb As RadioButton In RadioTitles
rb.Parent.Enabled = False
rb.Tag = rb.Parent
AddHandler rb.CheckedChanged, AddressOf TitleRadioButtons_CheckedChanged
Dim pt As Point = Me.PointToClient(rb.PointToScreen(New Point(0, 0)))
Me.Controls.Add(rb)
rb.Location = pt
rb.BringToFront()
Next
End Sub
Private Sub TitleRadioButtons_CheckedChanged(sender As Object, e As System.EventArgs)
Dim rb As RadioButton = DirectCast(sender, RadioButton)
If Not IsNothing(rb.Tag) AndAlso TypeOf rb.Tag Is Control Then
Dim ctl As Control = DirectCast(rb.Tag, Control)
ctl.Enabled = rb.Checked
End If
End Sub
Unfortunately, that's not how radio buttons work.
As I'm sure you're aware, radio buttons get their grouping from containers. To my knowledge, if you want to be able to accomplish what you're asking, you'll probably need to code a custom solution. For instance, you could place an event handler on each radio button to fire off the same event to uncheck other boxes, e.g.
radioButton1.CheckedChanged += anyRadioButton_CheckedChanged;
radioButton2.CheckedChanged += anyRadioButton_CheckedChanged;
radioButton3.CheckedChanged += anyRadioButton_CheckedChanged;
...
private void anyRadioButton_CheckedChanged(object sender, EventArgs e)
{
foreach (var control in this.Controls)
{
if(control is GroupBox)
{
foreach (var childControl in ((GroupBox)control).Controls)
{
if (childControl is RadioButton && childControl != sender)
{
((RadioButton)childControl).Checked = false;
}
}
}
}
}
You can set text of the GroupBox to empty string and put RadioButton over it. The trick is to put it on form actually but move it the way it looks like part of GroupBox. But this is sufficient only if your group boxes are static and won't be moved. Otherwise it is better to use solution which #matzone proposed. But even in that case you can put all radio buttons in collection in your code and use the only event handler for all of them. Something like
private List<RadioButton> radioButtons;
public YourFormConstructor()
{
InitializeComponent();
radioButtons.Add(radio1);
radioButtons.Add(radio2);
radioButtons.Add(radio3);
foreach (var radio in radioButtons)
radio.CheckedChanged += RadioCheckedChanged;
}
private void CheckedChanged(object sender, EventArgs e)
{
var thisRadio = sender as RadioButton;
if (!thisRadio.Checked)
return;
foreach (var radio in radioButtons)
if (radio != thisRadio)
radio.Checked = false;
}

Enable/Disable selectAll check box in Infragistics UltraWinGrid

I have used Infragistics UltraWinGrid to display data on the Grid. In this grid, there is one check box column. I have added check box in header in this column to selectAll option.
Now I want to enable/disable this header check box on any button click event to restrict user to perform any action.
Can any one tell me how to do this?
Thanks in advance.
I can think of two options off the top:
1) If you don't want the user to click any of the checkboxes, just hide the entire column if they are not authorized.
2) If you only want to keep the user from selecting all of the items in the grid, add code to the selectAll method to ignore the request if the user is not authorized.
Update
3) If you are using a version of the grid that supports it, you can use:
grid.DisplayLayout.Override.HeaderCheckBoxVisibility = HeaderCheckBoxVisibility.Never
when the form containing the grid is loaded if the user is not authorized.
The check box in the header is provided by a HeaderCheckBoxUIElement and this has an enabled property that can be set to determine if the check box is enabled. To get a reference to the HeaderCheckBoxUIElement you can use the MouseEnterElement and set the Enabled property in that event. For tracking if it is enabled you could use the Tag property of the column.
The code in VB:
Private Sub Button1_Click(sender As Object, e As System.EventArgs) Handles Button1.Click
Dim col As UltraGridColumn = Me.UltraGrid1.DisplayLayout.Bands(0).Columns("OnSite")
col.Tag = (TypeOf col.Tag Is Boolean AndAlso CBool(col.Tag) = False)
End Sub
Private Sub UltraGrid1_MouseEnterElement(sender As Object, e As Infragistics.Win.UIElementEventArgs) Handles UltraGrid1.MouseEnterElement
If TypeOf e.Element Is HeaderCheckBoxUIElement Then
Dim element As HeaderCheckBoxUIElement = DirectCast(e.Element, HeaderCheckBoxUIElement)
element.Enabled = (TypeOf element.Column.Tag Is Boolean AndAlso CBool(element.Column.Tag) = True)
End If
End Sub
The code in C#:
void ultraGrid1_MouseEnterElement(object sender, UIElementEventArgs e)
{
if (e.Element is HeaderCheckBoxUIElement)
{
HeaderCheckBoxUIElement element = (HeaderCheckBoxUIElement)e.Element;
element.Enabled = (element.Column.Tag is bool && (bool)element.Column.Tag == true);
}
}
private void button1_Click(object sender, EventArgs e)
{
UltraGridColumn col = this.ultraGrid1.DisplayLayout.Bands[0].Columns["OnSite"];
col.Tag = (col.Tag is bool && (bool)col.Tag == false);
}

Unwanted Retriggering of Textbox Events

This is one of those "seems obvious" as how to do, but came across interesting side effect in implementing. I'm trying to keep two text boxes syncronized when information is updated. In this example, I will be using txtStartDate and txtEndDate. If the txtStartDate is changed, then the txtEndDate should should be updated. Likewise, if the txtEndDate changes, I want the txtSartDate to be updated. The side effect I'm comming across is that when I set them up under the TextChanged event for both, the events seem to retrigger against each other indefinately (endless loop?). Am I using the wrong event? Is this a task for a delegate?
You need an extra condition. Depending on your setup, that could be "only change other value when I have focus".
A more general solution (I hope you can read C#):
private bool changingTextBox1 = false;
void textBox1TextChanged(object sender, EventArgs e)
{
if (! changingTextBox1)
{
changingTextBox1 = true;
try
{
// do stuff
}
finally
{
changingTextBox1 = false;
}
}
}
A very basic way of solving it would be that you create an UpdateInProgress boolean member variable in the Form and at the start of each of the event handlers you check if it's true and if so you just ignore the event, otherwise set it to true and then set it to false at the end of the event.
Dim bUpdating As Boolean = False
Private Sub txtStartDate_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtStartDate.TextChanged
If Not bUpdating = True Then
bUpdating = True
Try
'do stuff
Catch ex As Exception
Finally bUpdating = False
End Try
End If
End Sub

How to require CheckedListBox to have at least one item selected

With the CheckListBox in VB.NET in VS2005, how would you make it compulsory that at least one item is selected?
Can you select one of the items at design time to make it the default?
What would be best way to handle the validation part of this scenario? When should the user be required to tick one of the boxes?
Either idea -- preventing the user from unchecking the last checked item, or validating that at least one item is checked before proceeding -- is fairly straightforward to implement.
How to prevent the user from unchecking the last checked item
1. Make sure at least one item is checked to begin with (e.g., in your form's Load event):
Private Sub frm_Load(ByVal sender As Object, ByVal e As EventArgs)
clb.SetItemChecked(0, True) ' whatever index you want as your default '
End Sub
2. Add some simple logic to your ItemCheck event handler:
Private Sub clb_ItemCheck(ByVal sender As Object, ByVal e As ItemCheckEventArgs)
If clb.CheckedItems.Count = 1 Then ' only one item is still checked... '
If e.CurrentValue = CheckState.Checked Then ' ...and this is it... '
e.NewValue = CheckState.Checked ' ...so keep it checked '
End If
End If
End Sub
How to validate that at least one item is checked
Private Sub btn_Click(ByVal sender As Object, ByVal e As EventArgs)
' you could also put the below in its own method '
If clb.CheckedItems.Count < 1 Then
MsgBox("You must check at least one item.")
Return
End If
' do whatever you need to do '
End Sub
You can, but the user can always uncheck it.
The way I would do this would be on submit, loop through the checkbox items and make sure at least one of them was checked (break the loop after as your requirement is met, no need to process the rest of the list.)
If the check fails, make a custom validator visible. Required field validators don't work with Check List Boxes, but you can see how they implemented here:
http://www.codeproject.com/KB/webforms/Validator_CheckBoxList.aspx
This solution should be close. Not 100%, there is no easy way to find out that items were added to an empty list. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of your toolbox onto your form. No additional code is needed.
Public Class MyCheckedListBox
Inherits CheckedListBox
Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)
REM Ensure at least one item is checked
MyBase.OnEnter(e)
If Me.CheckedIndices.Count = 0 AndAlso Me.Items.Count > 0 Then
Me.SetItemChecked(0, True)
End If
End Sub
Protected Overrides Sub OnItemCheck(ByVal e As ItemCheckEventArgs)
REM Prevent unchecking last item
If Me.CheckedIndices.Count <= 1 AndAlso e.NewValue = CheckState.Unchecked Then e.NewValue = CheckState.Checked
MyBase.OnItemCheck(e)
End Sub
End Class
Optional additional override that ensures an item is checked when the form first shows up:
Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
MyBase.OnHandleCreated(e)
If Me.CheckedIndices.Count = 0 AndAlso Me.Items.Count > 0 Then
Me.SetItemChecked(0, True)
End If
End Sub
Trap ItemCheck event and verify if last checkbox is unchecked:
private void Form1_Load(object sender, EventArgs e)
{
checkedListBox1.SetItemChecked(0, true);
checkedListBox1.ItemCheck += checkedListBox1_ItemCheck;
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (CountChecked() == 1 &&
e.NewValue == CheckState.Unchecked &&
e.CurrentValue == CheckState.Checked)
{
checkedListBox1.SetItemChecked(0, true);
}
}
private int CountChecked()
{
int count = 0;
for (int i = 0; i < checkedListBox1.Items.Count; i++)
{
if (checkedListBox1.GetItemChecked(i) == true)
count++;
}
return count;
}
Updated: Then you have to make async call to set item check state back.
private delegate void SetItemCallback(int index);
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (checkedListBox1.CheckedIndices.Count == 1 &&
e.NewValue == CheckState.Unchecked &&
e.CurrentValue == CheckState.Checked)
{
int index = checkedListBox1.CheckedIndices[0];
// Initiate the asynchronous call.
SetItemCallback d = new SetItemCallback(this.SetItem);
d.BeginInvoke(index, null, null);
}
}
private void SetItem(int index)
{
if (this.checkedListBox1.InvokeRequired)
{
SetItemCallback d = new SetItemCallback(SetItem);
this.Invoke(d, new object[] { index });
}
else
{
checkedListBox1.SetItemChecked(index, true);
}
}
Validation Scenario Suggestion :
If your winform has an Submit/Save button, I would like the application to show an error when the button is cliked (provided there is a label saying that there should be atleast one item selected). The error message need not be a MessageBox, it could a red label showing the error description in the form itself
If there are no buttons you can hold on to, then do the validation as when users click on the items. Using ItemCheck event, check if there is atleast one item checked. If not, show the error label.
Code :
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (checkedListBox1.CheckedIndices.Count == 1 &&
e.NewValue == CheckState.Unchecked &&
e.CurrentValue == CheckState.Checked)
{
//Show error label in red
}
}
I would use the ItemCheck event to set the Button.Enabled = false when there is not at least one item checked, and Button.Enabled = true when there is at least one item checked (assuming an Ok button or something alike).