I would like to create a handler which listens GetFocus / LostFocus events for all TextBoxes in a Form using VB6 how can I achieve that?
What i tried so far:
Option Explicit
Dim Cnt As Control
Private WithEvents Txt As VB.TextBox
Private Sub Form_Load()
For Each Cnt In Me.Controls
If TypeOf Cnt Is TextBox Then
Set Txt = Cnt
End If
Next Cnt
End Sub
Private Sub Txt_GotFocus()
Txt.BackColor = &H80000018
End Sub
Private Sub Txt_LostFocus()
Txt.BackColor = &H80000005
End Sub
but this only works for one TextBox in the Form
this only works for one TextBox in the Form because Txt can only refer to one textbox at a time.
One way to have a common handler is to create your Texboxes as a control array. Give them all the same name (ie txtBox). VB will automatically make an array of them. You can control their order in the array using the Index property. Now, your LostFocus will look like this:
Private Sub txtBox_LostFocus(Index As Integer)
txtBox(Index).Backcolor = &H80000005
End Sub
If you need to change what you do based on WHICH textbox it is, use the Index to tell which one it is. NOTE: Control arrays are quite handy, but they disappear in VB.NET. There are some equivalent methods but I would not get too attached to the exact way they work.
For more complex ops, the several events can call a common procedure passing the control as an argument.
Related
I am writing a macro to automate tasks on a spreadsheet in LibreOffice Calc.
One of those tasks is simply to add the numbers contained in a given cell range and write the total in the appropriate cell when one of these cells is edited. (The cells actually contain text: the names of different services. The program then fetches the number of hours associated with each service's name to add them all up.)
Editing such a cell triggers the Modify_modified(oEv) event listener.
The listener then calls the subroutine UpdateTotalHoursOfAgent(calendarSize, allServices, agentTopleftCell) which performs the task described above.
The problem is that arguments calendarSize and allServices, which are defined in other places in the code, are out of scope in the event listener.
I do not know how to pass those arguments to the listener.
I tried using global variables instead even though it is frowned upon, but I suspect that they reach the end of their lifetime when the main program's execution is complete, and are not available anymore when a cell is edited afterwards.
How can I pass arguments calendarSize and allServices to the UpdateTotalHoursOfAgent subroutine when Modify_modified(oEv) is triggered?
Here's part of the code used to create the event listener (found on a forum):
Private oListener, cellRange as Object
Sub AddListener
Dim sheet, cell as Object
sheet = ThisComponent.Sheets.getByIndex(0) 'get leftmost sheet
cellRange = sheet.getCellrangeByName("E4:J5")
oListener = createUnoListener("Modify_","com.sun.star.util.XModifyListener") 'create a listener
cellRange.addModifyListener(oListener) 'register the listener
End Sub
Sub Modify_modified(oEv)
' *Compute agentTopleftCell*
REM How to obtain calendarSize and allServices from here?
UpdateTotalHoursOfAgent(calendarSize, allServices, agentTopleftCell)
End Sub
Sub Main
' *...code...*
Dim allServices As allServicesStruct
Dim calendarSize As calendarStruct
AddListener
' *...code...*
End Sub
I tried using global variables...
Probably you did not do it correctly. Here is how to set a global variable.
Private oListener, cellRange as Object
Global AllServices
Type allServicesStruct
svc As String
End Type
Sub AddListener
Dim sheet, cell as Object
sheet = ThisComponent.Sheets.getByIndex(0) 'get first sheet
cellRange = sheet.getCellrangeByName("E4:J5")
oListener = createUnoListener("Modify_","com.sun.star.util.XModifyListener")
cellRange.addModifyListener(oListener)
End Sub
Sub Modify_modified(oEv)
MsgBox AllServices.svc
End Sub
Sub Main
Dim allServicesLocal As allServicesStruct
allServicesLocal.svc = "example"
AllServices = allServicesLocal
AddListener
End Sub
Result:
This was adapted from my answer at https://stackoverflow.com/a/70405189/5100564
Reference Link to "CanRaiseEvent" property: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.canraiseevents?view=netframework-4.8#System_Windows_Forms_Control_CanRaiseEvents
Given that events in VB6 are recursive, I would like to disable a given controls event, while in a event handler sub.
For example, currently, if in a TextBox change event the contents of the TextBox are changed (say to "test") another event will fire causing the state of the current event to be stacked and the TextBox change event will be called again. I can prevent the event from repeating the code with a simple "If" (e.g. If Text1.Text = "test" Then Exit Sub). This will immediately exit the second event and return to the state of the first event to continue processing. However, I would prefer to disable the TextBox event on entering the initial event. It would appear that the property cited in the reference link would accomplish this, but it is not recognized by VB6.
Is there something I need to declare first to use the "CanRaiseEvent" property in VB6?
Thanks X
The typical way this is done is with a Static ... Boolean variable:
Sub SomeEvent
Static inHere As Boolean
If inHere Then
Exit Sub
Else
inHere = True
End If
.....
inHere = False
End Sub
You can do it kind of like this, but it might not be worth the ffort. (And by the way, I don't have the VB6 IDE on this PC so this will be un-syntax checked but it should get you started.
Within your Form (and for this example let's say the textbox you care about is txtFirstName):
Private WithEvents mFirstNameEventListener As Textbox
Private Sub mFirstNameEventListener_TextChanged() 'or whatever the event is called in VB6
DisableEvents
'do work here, like forcing to upper-case etc
EnableEvents
End Sub
Sub Form_Load()
EnableEvents
End Sub
Sub Form_Unload()
DisableEvents
End Sub
Private Sub EnableEvents()
Set mFirstNameEventListener = txtFirstName
End Sub
Private Sub DisableEvents()
Set mFirstNameEventListener = Nothing
End Sub
Also note, you would not use the standard VB6 stubbed out event handler too. you would only do it this way. (Otherwise both event handlers would get called)
Bit of a rookie issue here. How do you deal with data verification using Access Events? The problem is that when I use SetFocus to return the Cursor to the field with the errant data, Access goes through the _Exit and/or _LostFocus Events of the next Control in the Tab Order. If those include data validation procedures, the desired SetFocus is circumvented.
Using techturtles answer, I came up with this "solution" (read "hack").
Private Sub ServicingEmployee_LostFocus() 'Check data validity
If dataEntryCancelled Then Exit Sub
Dim cntrl As Control
Set cntrl = Me.ServicingEmployee
Dim myResponse As Integer
If IsNull(cntrl.Value) Then
myResponse = MsgBox("This field cannot be left blank.", vbOKOnly, "Enter Date Collected")
dataErrorInPreviousField = True
**'Next line sets a Public Control**
Set controlWithDataEntryError = Me.ServicingEmployee
End If
End Sub
Private Sub Client_GotFocus() '**Check for data Error in previous Control according to Tab Order**
If dataErrorInPreviousField Then Call dataErrorProc
End Sub
Public Sub dataErrorProc()
With controlWithDataEntryError
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
dataErrorInPreviousField = False
End Sub
Private Sub Client_Exit(Cancel As Integer) '**Example of Bypassing _Exit Event**
If dataEntryCancelled Or dataErrorInPreviousField Then Exit Sub
.
...
End Sub
My question is this: Is there a simpler way to accomplish this?
First, I would strongly encourage you to add table constraints in addition to your form validation, if you haven't already done so. Assuming this form is tied to a table in Access, just make sure the ServicingEmployee field (in the table) is set to required. That way, you have another level of validation making sure the field has a value.
For form validation, you can use either the form's or control's BeforeUpdate event. If you use the form's event, you only have to run the check once.
I would also suggest having a separate function (within the form) to check validity. Something like this (here, I'm using a form tied to an Employees table, with a first name and last field which should both be non-null):
Private Function isFormValid() As Boolean
' we start off assuming form is valid
isFormValid = True
If IsNull(Me.txtFirstName) Then
MsgBox "First name cannot be blank", vbOKOnly
Me.txtFirstName.SetFocus
isFormValid = False
ElseIf IsNull(Me.txtLastName) Then
MsgBox "Last name cannot be blank", vbOKOnly
Me.txtLastName.SetFocus
isFormValid = False
End If
End Function
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Not isFormValid Then
Cancel = 1
End If
End Sub
Hi i am fairly new to visual studio and I am having some trouble adding a save feature to my program. Basically what my program does is set event reminder for the user (it's like a daily planner but with no notifications). I have got the "add event', "delete", and "update buttons to work on the program and now all I have left is the "save" and "load" key. What I am trying to do is find a way to save the DataGridView so it can be opened back up in the program at a later date using the "load" key. If it would be easier to just remove the "load" and save the info right into the event reminder application, I could go that route but I don't have the first idea how to do that. This is what I have right now for the code in the main form:
Public Class MainForm1
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
If DataGridView1.Columns(e.ColumnIndex).Name = "Delete" AndAlso Me.DataGridView1.Rows(e.RowIndex).IsNewRow = False Then
Me.DataGridView1.EndEdit()
Me.DataGridView1.Rows.RemoveAt(e.RowIndex)
End If
If DataGridView1.Columns(e.ColumnIndex).Name = "Column4" AndAlso Me.DataGridView1.Rows(e.RowIndex).IsNewRow = False Then
Dim Update As UpdateWindow
Update = UpdateWindow
Update.Show()
End If
End Sub
Private Sub dltBtn_Click(sender As Object, e As EventArgs)
Dim dltBtn As dltWindow
dltBtn = dltWindow
dltBtn.Show()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Button1 As addBtn
Button1 = addBtn
Button1.Show()
End Sub
Private Sub UptBtn_Click(sender As Object, e As EventArgs)
Dim UptBtn As UpdateWindow
UptBtn = UpdateWindow
UptBtn.Show()
End Sub
Dim thisFilename As String = Application.StartupPath & "\Event reminder.dat"
Private Sub saveBtn_Click(sender As Object, e As EventArgs) Handles saveBtn.Click
Me.Validate()
Me.SaveGridData(DataGridView1, thisFilename)
End Sub
Private Sub BtnLoad_Click(sender As Object, e As EventArgs) Handles BtnLoad.Click
Me.LoadGridData(DataGridView1, thisFilename)
End Sub
Private Sub SaveGridData(ByRef ThisGrid As DataGridView, ByVal thisFilename As String)
ThisGrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText
ThisGrid.SelectAll()
IO.File.WriteAllText(thisFilename, ThisGrid.GetClipboardContent().GetText.TrimEnd)
ThisGrid.ClearSelection()
End Sub
Private Sub LoadGridData(ByRef ThisGrid As DataGridView, ByVal Filename As String)
ThisGrid.Rows.Clear()
For Each THisLine In My.Computer.FileSystem.ReadAllText(Filename).Split(Environment.NewLine)
ThisGrid.Rows.Add(Split(THisLine, " "))
Next
End Sub
End Class
I have gone on the other forums and asked about how to do this and they all said the bind the datagridview to a data table. If that is the route I have to go, how do I go about it? If anyone has some examples or code I could try out I would be much appreciative.
The problem I can see in the posted code is between the LoadGridData and the SaveGridData methods. When the LoadGridData method runs, it reads a file where each line is “Split” on spaces “ “. This is fine and if the file is properly “space” delimited it works as expected. So let us assume everything works and the data displays in the grid properly.
Then the user presses the saveBtn which calls the SaveGridData method. The code appears to “select” all the cells in the DataGridView to the clipboard, then proceeds to write the clipboard text to the SAME file name used when loading the grid. This also appears to work as expected.
The problem with this is that when the code copies all the cells of the DataGridView to the clipboard then, writes this copied clipboard text; it will use a “Tab” delimiter. The file Event reminder.dat which was read in using a “space” delimiter will be overwritten with a “tab” delimiter and not a “space” delimiter. Therefore, then next time the load method is run… it will fail.
If the load and save button are going to read the same file, then they MUST both agree on what character is used as a delimiter. You can use whatever character you want as a delimiter. You can use a “space” as the posted code does, however this limits each type to a string with no spaces. If one of the fields is a home address, chances are good, there will be a space in the string. Using a “space” as a delimiter is not necessarily the best of choices.
A simple solution is available with the current code. Currently if the file Events reminder.dat is space delimited and reads in properly to the data grid view, then it should be easy to replace the space delimiter with something else. In this case, since the SaveGridData method is delimiting the fields with a “tab” then we simply need to change the load method to “split” on “Tabs” and not “spaces” when reading back the file.
Step 1 – make a copy of Events reminder.dat
Step 2 – Run the program, press the load button and make sure all the data is properly loaded into the grid.
Step 3 – If the data is displayed properly in the grid, press the save button. (Updates file with tab delimiters)
Step 4 – Exit the program and make the change below in the LoadGridData method.
ThisGrid.Rows.Add(Split(THisLine, vbTab))
Step 5 – Run the program. Now both the load and write methods agree on what character (Tab) is used as a delimiter.
Lastly, to comment on using a DataTable the answer would be yes. DatagridViews and DataTables play nicely together. Hope this helps.
You may want to take a “Tour” of Stackoverflow to see how it works.
I have an event procedure which is to be run when user selects a certain record.
If I put it in an ON CURRENT event, it works.
However, I don't need it to run when the form is being opened. That is, when the form is opened and the records are loaded onto the form, my event procedure is called up for every
record that is loaded. It slows my program down as this is unnecessary.
My question is, how do I check that the event is not run during the "loading" of the records.
something like:
on_current_event do:
if event is not during on_load then
do this
end if
end proc
Why not declare a form level boolean variable and set it to true when the form has finished loading?
In your OnCurrent event you can then do something like:
If Variable then
Do Stuff
Else
Don't do stuff
End If
This is probably not addressing the underlying issue however...
In your form's code:
Private isLoading As Boolean
Private Sub Form_Open(Cancel as Boolean)
isLoading = True
End Sub
Private Sub Form_Current()
If isLoading Then
isLoading = False
Exit Sub
End If
End Sub
That way the first occurence of the OnCurrent event when the form is opening will be bypassed.
That being said, it looks to me that this is not your real underlying problem but you will need to give more information (open another question) if you want other people to try to help.