DataGridView: EditOnEnter selection mode allowing row deletion - vb.net

By default, a DataGridView is set to EditOnKeystrokeOrF2 edit mode. This means two or three clicks (spaced further apart than the user's double click interval) are required to change the value of a combobox in this view. As this is rather strange for a UI object, you would tend to think the control doesn't work.
Fortunately, you can change the selection mode to EditOnEnter. This will immediately select a cell when clicking on it, not first select the row, reducing the amount of clicks by 1. However, DataGridViews are implemented somewhat strangely. There is a '-1th' cell that is not manually selectable in each row.
When this '-1th' cell is selected, in the normal selection mode the row is selected, but in the "EditOnEnter" mode the 1st cell in the row is selected instead. If the DataGridView is set to enable row deletion using the "Del" key, then using EditOnEnter makes it impossible to use this functionality.
How do I get both to work? I.e.: I don't have a view where the user can have click up to 6 times (users tend to click more rapidly when they have to do so a lot of times) to open a box, while at the same time allowing row selection using the special -1th column?

One would need to programmatically toggle between both edit modes when any cell in the row is clicked. However, the CellClick event fires too late: after the row is already selected. Naively just toggling the EditMode would mean that the first click on the row selection box doesn't work, while the second would, which would appear as buggy behaviour.
The trick is to do much more manually. The following event handler, when attached to the CellClick event, will resolve almost all issues.
Private Sub CellSelect(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs) Handles myDataGridView.CellClick
Dim dgv As DataGridView = CType(sender, DataGridView)
If dgv.Rows.Count = 0 Then
Return
End If
Dim rowToSelect As Integer = e.RowIndex
Dim columnToSelect As Integer = e.ColumnIndex
If e.RowIndex = -1 Then
rowToSelect = 0
End If
If rowToSelect >= dgv.Rows.Count Then
rowToSelect = 0
End If
If columnToSelect = -1 Then
dgv.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2
dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect
dgv.CurrentCell = Nothing
dgv.Rows(rowToSelect).Selected = True
Else
If columnToSelect >= dgv.Rows(rowToSelect).Cells.Count Then
columnToSelect = 0
End If
dgv.EditMode = DataGridViewEditMode.EditOnEnter
dgv.SelectionMode = DataGridViewSelectionMode.CellSelect
dgv.Rows(rowToSelect).Cells(columnToSelect).Selected = True
End If
End Sub
It works by unsetting the selected cell, then setting the selected row programmatically. As the EditMode was changed beforehand, it will select the entire row, not just the first cell, even the first time that the row-selection box is clicked.
There are also a whole bunch of edge cases where a user that clicks quickly enough can create click events on cells that do not exist. So we assume those clicks are on the cell [0,0] so at least our application won't blow up.
This isn't a perfect solution (yet). With this solution: A small graphical glitch remains; for about one frame the DataGridView will flicker between edit modes, very briefly appearing as though the row is selected.

Related

Is it possible to display radio buttons without one being selected?

I have two radio buttons in a group box. I don't want either of them selected, so the user is forced to make a choice. (I have code in place to ensure one has been selected before moving to the next form.)
I set the Checked property to False for both buttons, but when I run the form it still displays the top button as selected. I added to following code to the Load event of the form but it STILL shows one as selected.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
RBArchive.Checked = False
RBCopy.Checked = False
End Sub
How can I get both radio buttons to remain unselected?
In the designer just set the default for each button = false. The other issue is that the Group cannot be the FIRST control on your form (tab index = 0). If the radio group is the first control, then put/place/move something else to hold the tab index 0 position.
If the grouping is the first control, then on form load the first of the group will be selected - even if all radio buttons are defaulted to false. However, if the group control is NOT the first control on the form, then you should not have to do anything other then just set the controls to false. This is for winforms, and not WPF.
In asp.net? then setting the group to -1 works rather well. So if this is WPF, then I would give setting the group control to -1 a try. In standard winforms, the group control does not spit out a index number (like it does for asp.net grouped radio buttons).
So, I tend to use this:
Private Function GetGroupBox(grpb As GroupBox) As Integer
' return seleted control - starts at 0
For Each ctrl As RadioButton In grpb.Controls
If ctrl.Checked Then Return ctrl.TabIndex
Next
Return -1 ' if none checked
End Function
And above assumes that the tabindex for all radiobuttons in the group is set to 1
So, in code, if there are 5 buttons, I can go:
If GetGroupBox(Me.gRadioGroupProvider) = 0 Then
strProvider = "Microsoft.ACE.OLEDB.12.0"
Else
strProvider = "Microsoft.Jet.OLEDB.4.0"
End If
So, now you get a group return value like you get in asp.net, VB6/VBA
Regardless, if all radio buttons have a default of false, and the radio group is not the first control on the form, then none should show as selected.
If this is WPF, then all bets are off.

Taborder jumping between frames

I have a userform containing 3 frames that all include several textboxes. I would like to be able to tab the textboxes across frames.
So, something like this: Frame1 - Textbox1 > Frame2 - Textbox1 > Frame3 - Textbox1 > Frame1 - Textbox2 > Frame1 - Textbox3 > Frame2- Textbox2
The textboxes are dynamically added and stored in an array according to the desired taborder, so the desired order is easily accesible. I just cant seem to find a way to apply this.
It would of course be possible to change the frame layout. However, the frames are used both to control the placement of the textboxes, and also to add separate scrollbars if the amount of textboxes exceeds the frame area.
Is something like this possible? Any help or suggestions much appreciated.
Edit: Added picture of the 3 frames
Frames
Edit2:
I think destination-datas comment put me on the right track.
I created a class module
Public WithEvents TxtBox1 As MSForms.TextBox
Private Sub TxtBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
MsgBox ("Test")
End Sub
Private Sub TxtBox1_change()
MsgBox ("Test")
End Sub
And then in the sub that generates the textboxes, for the textboxes where I want to jump frames I do:
Dim tabArray1() As New TabBox1
Dim inputfelt As MSForms.TextBox
Set inputfelt = Hovedvindu.SkjemaFrame.SenderFrame.Controls.Add("Forms.TextBox.1", "M" & i & "SenderNavn", True)
Set tabArray1(i).TxtBox1 = inputfelt
This adds all the correct textboxes to an array so that I can make an Exit event that changes focus, which I think should not be too diffucult.
However, I cant seem to get the events to fire properly.
The change event seems to work when I change the textboxes with a sub, for example when I tested that the correct textboxes are added to the array by looping through and changing the text of the textboxes in the array. But when I change the textboxes manually, nothing happens. The exit event doesnt seem to work at all.
Im not too experienced with event handling, so I might have missed something.
I found an acceptable solution. The approach described in Edit 2 of the original post worked once I moved the definition of the TabArray() outside the sub, so that I keep track of the textboxes after the sub is ended.
I have one array, and one class module for the "end" textboxes on each of the 3 frames. The class modules tracks keydown on Tab. The 3 class modules are identical, except that they store the tabindex of the textbox from where the focus jumps to another frame in 3 different hidden labels on the userform. Keeping track of the tabindex allows me to know where to start when focus comes back to a frame.
the 3 class modules look like this:
Public WithEvents TxtBox1 As MSForms.TextBox
Private Sub TxtBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal
Shift As Integer)
If KeyCode = vbKeyTab Then
'store tabindex
Hovedvindu.Controls("SisteTab1").Caption =
Hovedvindu.SkjemaFrame.SenderFrame.ActiveControl.TabIndex
'call sub that moves the focus
flyttfokus (1)
End If
End Sub
The sub that moves focus is shown below. Input is just an integer (1,2 or 3) depending on which class module the call comes from, and thus which frame to move from/to. The sub reads the 3 hidden labels to know which textbox to start from when the focus is moved to a new frame. Error handling in the last case is for when the focus is moved from the very last textbox in the 3d frame. This will try to move focus to a non-existing textbox in the first frame, which will throw an error which can be ignored.
Sub flyttfokus(SisteFrame As Integer)
Dim St1 As Integer
Dim St2 As Integer
Dim St3 As Integer
St1 = CInt(Hovedvindu.Controls("SisteTab1").Caption)
St2 = CInt(Hovedvindu.Controls("SisteTab2").Caption)
St3 = CInt(Hovedvindu.Controls("SisteTab3").Caption)
Select Case SisteFrame
Case 1
Hovedvindu.SkjemaFrame.MottakerFrame.Controls(St2 + 1).SetFocus
Case 2
Hovedvindu.SkjemaFrame.InfoFrame.Controls(St3 + 1).SetFocus
Case 3
On Error Resume Next
Hovedvindu.SkjemaFrame.SenderFrame.Controls(St1 + 1).SetFocus
On Error GoTo 0
End Select
End Sub
This solution will work as desired when starting from the very first textbox. If the user however, mouseclicks to a textbox for example on the second line and then starts tabing, focus will be moved back to the first line when focus is moved between frames. This is because the variables (labels) that keeps track og the Tabindex from where to start when a frame receives focus are only updated as the user tabs through the sheet. It is probably possible to update these variables on mousclick to a textbox, based on the amount of textboxes that are generated by the program. For my purposes this is not deemed necessary.

VB Datagridview cellvalidating event to check if active cell is empty berore leaving problems

In Visual basic i'm trying to make a field required by preventing the user to leave the active cell if it's empty.
The grid consists out of two columns. The first one is the ID which is automaticly filled in on adding a new row, the second one is the name.
On form load there are 4 items loaded into the grid. When the form is loaded the first cell that is selected passes the check with no problem. It is the same for the three cells that are filled on form load.
Unforunately it does not seem to work when i try to add a new row to the grid, enter a value and try to leave the cell. I've set a break point and checked the values. The value is equal to nothing... This is not the case with any of the already loaded values.
How does it not get the value i enter?
Thanks in advance.
Ivan.f
Private Sub ValidateCellValue(sender As Object, e As
DataGridViewCellValidatingEventArgs)
Handles grdResult.CellValidating
Dim catNaam = grdResult.Rows(e.RowIndex).Cells(1).Value
If IsNothing(catNaam) Then
MessageBox.Show("Veld mag niet leeg zijn")
e.Cancel = True
Exit Sub
End If
End Sub

Losing cell data when datagridview gets focus

I know I need to provide some code, but I'm not sure what I should show, so please suggest if you can.
I have a bound datagridview on a Windows form. After the form loads and the datagridview gains focus (on mouse click), the first row (and a specific column) loses it's data, changing the cell's state to dirty. It doesn't matter where I click to bring the dgv into focus, that row/column always goes blank. What event is firing that may trigger that loss of data?
Again, any suggestions as to what code to post would be great. I know that will help answer this question.
Edit #1
This code is an infinite loop, but I'm adding it in response to a comment:
Private Sub dgQCOrders_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles dgQCOrders.CellPainting
If e.RowIndex = 0 And e.ColumnIndex = 9 Then
If e.FormattedValue <> e.Value Then
MsgBox("Changed")
Else
MsgBox("Unchanged")
End If
End If
End Sub
Edit #2:
Private Sub dgQCOrders_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles dgQCOrders.CellPainting
If e.RowIndex = 0 And e.ColumnIndex = 9 Then
If e.FormattedValue <> e.Value Then
Me.txtTest.Text = "Changed"
Else
Me.txtTest.Text = "Unchanged"
End If
End If
End Sub
This test tells me that the new value is null, it is deleting that first record (which I already knew)--still don't know how to fix it!
Edit #3
More explanation:
Currently, the only event I'm handling is form_Load, which fills the dgv using the tableadapter for my (bound) dataset. I then bind the dgv to the binding source.
I know that this error only occurs when the dgv gains focus (I tested this by setting the focus to the dgv when the form loads). I have a series of checkboxes/listboxes/textboxes on this form as well that allows the user to filter the dgv dynamically (back-end, I filter the binding source). If I filter the dgv first, the same row and the same column (their indexes do not change) maintains it's value when I move the focus to the dgv. When I clear the filter, the same row and the same column, loses it's data again.
I did have the _CellStateChanged event firing after a user makes an edit. Currently, it is commented out so the data loss isn't reflected in my dataset.
Additionally, I have another dgv on a different form, bound the exact same way, with the _CellStateChanged event and everything fires and saves correctly. I have gone through the designer coding for both forms, I can't find any setting difference between the two.
I'm losing my mind over here! Any help is GREATLY appreciated!
I decided to recreate the form from scratch and this error is no longer occuring. I have compared the two sets of code and can't find one discrepancy between them. If anyone has this problem in the future, save time and recreate all of the objects related to your dgv.
Maybe I understand that statement within the focus cell is not saved in the database table if U save
this problem I solved
add blank textbox control to your form which contain dgv and named txtFocus
placed it behand dgv and use its properties send to back Or
Evoked by the bottom of the screen so do not show it
then
before save
white then :
txtFocus.Focus()
sendKeys.send("{F2}")
only in this case U can save the data inside last cell changed in dgv
best Reg
Ashraf

Rows Not Highlighting in Data Grid View

I have a simple selection form that displays a DataGridView and allows the user to select a record to process. I have a short sub routine that loops through the DGV and highlights rows based on a date comparison with a dictionary when the form loads. This works great the first time I open the form, but on subsequent form opens, the grid doesn't highlight. When I step through the code it looks like the rows should be highlighted, but when the form displays, nothing is highlighted.
Here is the code I'm using. I cannot figure out why it doesn't always work. Is there a better, more reliable way to accomplish this?
For Each row As DataGridViewRow In dgvPending.Rows
For Each pair In dPending
If row.Cells.Item("ID").Value = pair.Key Then
If row.Cells.Item("LAST_UPDATED").Value > pair.Value Then
row.DefaultCellStyle.BackColor = Color.BlanchedAlmond
End If
End If
Next
Next