Get Activecontrol inside multipage - vba

I am trying to get the name of theListbox that I just selected ("ListBox1"). Caveat: ListBox1 is located within Multipage1 (on the first tab).
Private Sub ListBox1_Click()
Dim m As String
m = Me.ActiveControl.Name
MsgBox (m)
End Sub
Since I have selected the ListBox1, I'm expecting the message box value to be ListBox1. Instead I am receiving Multipage1.
What should I be doing differently?

It helps to consider MultiPage, Pages, and Frames as subforms. If a subform control is active then the parent form will return the subform as the ActiveControl.
Here is the proper way to drill down to the actual ActiveControl.
Function ActiveControlName(Object As Object) As String
Dim Obj As Object
On Error Resume Next
Set Obj = Object.ActiveControl
If Err.Number = 0 Then
ActiveControlName = ActiveControlName(Object.ActiveControl)
Else
Set Obj = Object.SelectedItem
If Err.Number = 0 Then
ActiveControlName = ActiveControlName(Object.SelectedItem)
Else
ActiveControlName = Object.Name
End If
End If
On Error GoTo 0
End Function
Demo

An old post but I landed here with the sames issue. Unfortunately this answer didn't work for my scenario (controls positioned within nested frames on a multipage). But user6432984 gave a great headstart.
The key to the proper solution is that the active control (reported by "ActiveControl") is the top level control that is active on the userform. It may have either:
1) Another nested "ActiveControl",
2) A "SelectedItem"
3) Neither of the above - in which case it really is the "ActiveControl" you want.
So, the technique to drill-down requires that you determine which of 1), 2) or 3) above you have. If you don't have 3) keep throwing whichever of 1) or 2) you do have into the recursive function until you find 3):
Function ActiveControlName(Object As Object) As String
'modified my Malcolm Farrelle
Dim Obj As Object
Set Obj = Nothing
On Error Resume Next
Set Obj = Object.ActiveControl
On Error GoTo 0
If Obj Is Nothing Then
On Error Resume Next
Set Obj = Object.SelectedItem
On Error GoTo 0
End If
If Obj Is Nothing Then
ActiveControlName = Object.Name
Else
ActiveControlName = ActiveControlName(Obj)
End If
End Function

Related

Resume to a label not working as expected

I am having an issue using Resume and although I have found another solution by using an On Error GoTo, I am still confused as to why the code below doesn't work. The initial error occurs because the sheet name "Sheet_1" is already taken. This means that, in the watch window, err.number has a value of 1004 just before the Resume NameAgain is executed. Rather than clear the error and jump back up to the label, an error 20 occurs(resume without error), and the code moves to the End If line.
Given that there is an active error 1004, I can't understand why it acts as though there isn't an error. I have searched the site for Error 20 issues but nothing really resolved this for me or made me understand the logic behind it. Any help is much appreciated.
Sub ErrorTest()
Dim i as integer:i=1
NameAgain: On Error Resume Next
Worksheets("Main").Name = "Sheet_" & i
If Err.Number = 1004 Then
i = i + 1
Resume NameAgain
End If
End Sub
Update after paxdiablo comment:
The above was a poor attempt at trying to replicate but simplify a problem I was having. The section of code I am working with is below:
Activate CheckBook to use ActiveWindow
CheckBook.Activate
Set DestSheet = CheckBook.Worksheets.Add(After:=CheckBook.Sheets(1))
On Error Resume Next
v = 1
NameAgain: DestSheet.Name = ExpBookName & "_" & v
If Err.Number = 1004 Then
v = v + 1
Resume NameAgain
End If
On Error GoTo 0
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")
So the solution is to move the On Error Resume Next to the label line and use GoTo in place of Resume.
The resume statement is a means to, from within an error handler, go back to some point in your main (non-error-handling) code and resume execution.
In this case, you've explicitly stated you want to automatically resume next in the event of an error.
This is functionally equivalent to (VB-like pseudo-code):
line:
on error goto handler
cause error
resume line ' not in an error handler at this point '
handler:
resume next
So you're not technically in an error handler at the point where you try to resume to the label.
The right statement for what you're trying to do would be a simple goto rather than resume.
A more correct solution is to write code that does not deliberately generate errors or which does not use Goto.
Public Function GetNextSheetName(ByVal ipWb As Excel.Workbook, ByVal ipStemName As String) As String
Dim mySheet As Variant
Dim mySD As Scripting.Dictionary
Set mySD = New Scripting.Dictionary
For Each mySheet In ipWb.Sheets
' mySd.Count is a dummy entry to satisfy
' the Key and Item requirements for .Add.
' we are only interested in the Keys
' for use with the .Exists method later
mySD.Add mySheet.Name, mySD.Count
Next
Do
DoEvents
Dim myIndex As Long
myIndex = myIndex + 1
Dim myNextSheetName As String
myNextSheetName = ipStemName + "_" & CStr(myIndex)
Loop While mySD.Exists(myNextSheetName)
GetNextSheetName = myNextSheetName
End Function
Which now allows
Set DestSheet = checkbook.Worksheets.Add(After:=checkbook.Sheets(1))
DestSheet.Name = GetNextSheetName(checkbook, ExpBookName)
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")

check if textbox exists vba (using name)

I am using Ms-Access and I created a userform which has a number of Textboxes on it. The boxes are named: Box1, Box2, Box3 ...
I need to loop through all boxes, but I don't know which is the last one. To avoid looping through all userform controls I thought of trying the following:
For i =1 To 20
If Me.Controls("Box" & i).value = MyCondition Then
'do stuff
End If
Next i
This errors at Box6, which is the first box not found. Is there a way to capture this error and exit the loop when it happens.
I know I could use On Error but I 'd rather capture this specific instance with code instead.
Thanks,
George
A Controls collection is a simplified collection of controls (obviously) and share a same order as a placement order of controls.
First of all, even a creatable collection object lacks methods such as Exists or Contains , hence you need a function with error handling to checking/pulling widget from a collection.
Public Function ExistsWidget(ByVal Name As String) As Boolean
On Error Resume Next
ExistsWidget = Not Me.Controls(Name) Is Nothing
On Error GoTo 0
End Function
If you really doesnt like "ask forgiveness not permission" option you can pull entire ordered collection of your textboxes (and/or check existance by name in another loop with similar logic).
Public Function PullBoxes() As Collection
Dim Control As MSForms.Control
Set PullBoxes = New Collection
For Each Control In Me.Controls
If TypeOf Control Is MSForms.TextBox And _
Left(Control.Name, 3) = "Box" Then
Call PullBoxes.Add(Control)
End If
Next
End Function
Since names of widgets are unique - you can return a Dictionary from that function with (Control.Name, Control) pairs inside and able to check existance of widget by name properly w/o an error suppression.
There's a good guide to Dictionary if it's a new information for you.
Anyway, no matter what object you choose, if user (or code) is unable to create more of thoose textboxes - you can convert this Function above to a Static Property Get or just to a Property Get with Static collection inside, so you iterate over all controls only once (e.g. on UserForm_Initialize event)!
Public Property Get Boxes() As Collection
Static PreservedBoxes As Collection
'There's no loop, but call to PullBoxes to reduce duplicate code in answer
If PreservedBoxes Is Nothing Then _
Set PreservedBoxes = PullBoxes
Set Boxes = PreservedBoxes
End Property
After all, the last created TextBox with name Box* will be:
Public Function LastCreatedBox() As MSForms.TextBox
Dim Boxes As Collection
Set Boxes = PullBoxes
With Boxes
If .Count <> 0 Then _
Set LastCreatedBox = Boxes(.Count)
End With
End Function
I think that now things are clearer to you! Cheers!
Note: All code are definitely a bunch of methods/properties of your form, hence all stuff should be placed inside of form module.
Long story short - you cannot do what you want with VBA.
However, there is a good way to go around it - make a boolean formula, that checks whether the object exists, using the On Error. Thus, your code will not be spoiled with it.
Function ControlExists(ControlName As String, FormCheck As Form) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
ControlExists = (Err.Number = 0)
End Function
Taken from here:http://www.tek-tips.com/viewthread.cfm?qid=1029435
To see the whole code working, check it like this:
Option Explicit
Sub TestMe()
Dim i As Long
For i = 1 To 20
If fnBlnExists("Label" & i, UserForm1) Then
Debug.Print UserForm1.Controls(CStr("Label" & i)).Name & " EXISTS"
Else
Debug.Print "Does Not exist!"
End If
Next i
End Sub
Public Function fnBlnExists(ControlName As String, ByRef FormCheck As UserForm) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
fnBlnExists = (Err.Number = 0)
End Function
I would suggest testing the existence in another procedure per below: -
Private Sub Command1_Click()
Dim i As Long
i = 1
Do Until Not BoxExists(i)
If Me.Conrtols("Box" & i).Value = MyCondition Then
'Do stuff
End If
i = i + 1
Next
End Sub
Private Function BoxExists(ByVal LngID As Long) As Boolean
Dim Ctrl As Control
On Error GoTo ErrorHandle
Set Ctrl = Me.Controls("BoX" & LngID)
Set Ctrl = Nothing
BoxExists = True
Exit Function
ErrorHandle:
Err.Clear
End Function
In the above, BoxExists only returns true if the box does exists.
You have taken an incorrect approach here.
If you want to limit the loop, you can loop only in the section your controls reside e.g. Detail. You can use the ControlType property to limit controls to TextBox.
Dim ctl As Control
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Then
If ctl.Value = MyCondition Then
'do stuff
End If
End If
Next ctl
I believe the loop will be faster than checking if the control name exists through a helper function and an On Error Resume Next.
But this only a personal opinion.

Listbox Item Clearing is Not Working in VBA Excel

I have a two list boxes with one button when the user can click the button move all the list item from listbox1 to listbox2. when the listbox1 is becomes empty app is getting restarted IN EXCEL 2016.
My Code is
For i = 1 To ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).listCount
ThisWorkbook.Sheets("MultiSheet").ListBoxes(strTolb).AddItem ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).List(1)
ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).RemoveItem (1)
Next i
Here strFromLb is clearing the values but when it clearing last value my VBA App is excel has been restarted.
Then I have tried code to clear the listbox
ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).ControlFormat.RemoveAllItems
ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).Items.Clear
The error is
"Object doesnt supported to property or method"
Then
ThisWorkbook.Sheets("MultiSheet").ListBoxes(strFromlb).Clear
This code I got the 400 error. so kindly help me.
Worksheets("MultiSheet").ListBoxes(strFromlb).ControlFormat.‌​RemoveAllItems
Reference: The Complete Guide to Excel VBA Form Control ListBoxes
There are two type listbox controls for the worksheet Forms control and MsForms ActiveX control. getListBox will return get either one.
You code has a couple of syntax errors in it
Listbox.List returns a 0 based array
You don't use parentheses when using RemoveItem because it is a not a function
Dim lBoxFrom As Object, lBoxTo As Object
Set lBoxFrom = getListBox("MultiSheet", strFromlb)
Set lBoxTo = getListBox("MultiSheet", strTolb)
For i = 0 To lBoxFrom.ListCount - 1
lBoxTo.AddItem lBoxFrom.List(1)
lBoxFrom.RemoveItem 1
Next
or
lBoxTo.List = lBoxFrom.List
lBoxFrom.Clear
Sub Test()
Const WORKSHEET_NAME = "Sheet1"
Const strFromlb = "BoxFrom"
Const strTolb = "BoxTo"
Dim lBoxFrom As Object, lBoxTo As Object
Dim i As Integer
Set lBoxFrom = getListBox(WORKSHEET_NAME, strFromlb)
Set lBoxTo = getListBox(WORKSHEET_NAME, strTolb)
lBoxFrom.AddItem "A"
lBoxFrom.AddItem "B"
lBoxFrom.AddItem "C"
For i = 0 To lBoxFrom.ListCount - 1
lBoxTo.AddItem lBoxFrom.List(0)
lBoxFrom.RemoveItem 0
Next
End Sub
Function getListBox(WorkSheetName As String, ListBoxName As String) As Object
Dim lBox As Object
On Error Resume Next
Set lBox = Worksheets(WorkSheetName).ListBoxes(ListBoxName)
On Error GoTo 0
If lBox Is Nothing Then
On Error Resume Next
Set lBox = Worksheets(WorkSheetName).Shapes(ListBoxName).OLEFormat.Object.Object
On Error GoTo 0
End If
Set getListBox = lBox
End Function

How to get the HTML element and the position in the element's text where a person has clicked

Using internet explorer I would like to get the position where a person has clicked on text. An error of 3 to 4 characters is fine. The text is not editable and is usually in a span element.
I am aware I could set up a click event listener for the HTMLDocument however I do not always have the HTMLDocument object and thus may miss the event.
I have tried getting a IHTMLSelectionObject, then creating a text range with the IHTMLTxtRange, however when the web page is simply clicked as opposed to at least 1 character being selected then the IHTMLTxtRange has a parent of the HTMLBody and not of the element that was clicked.
The HTMLDocument.activeElement is also unreliable. In my tests it never actually returns the element clicked, it usually returns a major parent of the element somewhere up the tree.
Using MSHTML is there another way to achieve this?
I have also tried using the WIN API GetCursorPos however I do not know what to do with this position, I do not know how to convert this into the actual element.
EDIT:
I also thought of an interesting idea. When I need to know the element that has the cursor, I set a mouseDown or click event on the whole document. Then fire my own click and catch the event. In the IHTMLEventObj of the event is a FromElement which I had hoped would tell me where the cursor was. It seems it is always nothing for mouseDown and click events. For me at least this object is only used in for example mouseover events.
The following is what I have when at least a character is selected.
Private Function GetHTMLSelection(ByVal aDoc As IHTMLDocument2, ByRef htmlText As String) As Integer
Dim sel As IHTMLSelectionObject = Nothing
Dim selectionRange As IHTMLTxtRange = Nothing
Dim rangeParent As IHTMLElement4 = Nothing
Dim duplicateRange As IHTMLTxtRange = Nothing
Dim i As Integer
Dim x As Integer
Dim found As Boolean
Try
'get a selection
sel = TryCast(aDoc.selection, IHTMLSelectionObject)
If sel Is Nothing Then
Return -1
End If
'the range of the selection.
selectionRange = TryCast(sel.createRange, IHTMLTxtRange)
If selectionRange Is Nothing Then
Return -1
End If
'the the parent element of the range.
rangeParent = TryCast(selectionRange.parentElement, IHTMLElement4)
'duplicate our range so we can manipulate it.
duplicateRange = TryCast(selectionRange.duplicate, IHTMLTxtRange)
'make the dulicate range the whole element text.
duplicateRange.moveToElementText(rangeParent)
'get the length of the whole text
i = duplicateRange.text.Length
For x = 1 To i
duplicateRange.moveStart("character", 1)
If duplicateRange.compareEndPoints("StartToStart", selectionRange) = 0 Then
found = True
Exit For
End If
Next
If found Then
Debug.Print("Position is: " + x.ToString)
htmlText = duplicateRange.text
Return x
Else
Return -1
End If
Catch ex As Exception
Return -1
Finally
End Try
End Function
I cannot post answer with a nice function that shows how to do this but I will explain the important parts.
user the Win32 API GetCursorPos to get the point on the screen where the user last clicked.
If you have iFrames which means more than one HTMLDocument then you need to loop through your iFrames and use the HTMLFrameElement clientWidth and clientHeight along with a IHTMLWindow3 screenTop and screenLeft to find out which HTMLDocument your point is on.
Convert this point to a relative point using the IHTMLWindow you found in number 2.
Once you have the right HTMLDocument and a point relative to this document you can then use the elementFromPoint method on a IHTMLDocument2 object.
Once you have this you now know the point and element that was clicked on.
Private Function getElementTextPosition() As Boolean
Dim sel As IHTMLSelectionObject = Nothing
Dim selectionRange As IHTMLTxtRange = Nothing
Dim duplicateRange As IHTMLTxtRange = Nothing
Dim i As Integer = 0
Dim found As Boolean
Dim x As Integer
Try
'elementWithCursor is a IHTMLElement class variable
If elementWithCursor IsNot Nothing Then
ReleaseComObject(elementWithCursor)
elementWithCursor = Nothing
End If
'docWithCursor is also a IHTMLDocument2 class variable
'cursorPointInDoc is the point relative to the actual document
elementWithCursor = TryCast(docWithCursor.elementFromPoint(cursorPointInDoc.X, cursorPointInDoc.Y), IHTMLElement)
If elementWithCursor Is Nothing Then
Return False
End If
'get a selection
sel = TryCast(docWithCursor.selection, IHTMLSelectionObject)
If sel Is Nothing Then
Return False
End If
selectionRange = TryCast(sel.createRange, IHTMLTxtRange)
If selectionRange Is Nothing Then
Return False
End If
'First check if We have selection text so we will use that as the selected text
'_SelectedText relates to a class property
If selectionRange.text IsNot Nothing Then
_SelectedText = selectionRange.text
selectionRange.collapse(True)
Else
'the the parent element of the range.
selectionRange.moveToPoint(cursorPointInDoc.X, cursorPointInDoc.Y)
End If
'duplicate our range so we can manipulate it.
duplicateRange = TryCast(selectionRange.duplicate, IHTMLTxtRange)
'make the dulicate range the whole element text.
duplicateRange.moveToElementText(elementWithCursor)
'get the length of the whole text
i = duplicateRange.text.Length
For x = 0 To i
If duplicateRange.compareEndPoints("StartToStart", selectionRange) = 0 Then
found = True
Exit For
End If
duplicateRange.moveStart("character", 1)
Next
If found Then
'_CursorPositionInText is a class property and relates to the position where the person clicked in the html text.
_CursorPositionInText = x
_HTMLElementText = elementWithCursor.innerText
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function

Get position (in number) of selected item in dropdown list

In a dropdown list I have a few items. Can I, when I select an item, get the position of that item in the list as a number?
If you are looking for the index of a Data Validation list, this is what I'd do:
Put the following code in the ThisWorkbook module:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim ValidationIndex As Long
Dim rngTest As Excel.Range
'assumes the data validation is in a cell named "rngTest"
On Error Resume Next
Set rngTest = Sh.Range("rngTest")
If rngTest Is Nothing Then
Exit Sub
End If
On Error GoTo 0
If Not Intersect(ActiveCell, Sh.Range("rngTest")) Is Nothing Then
ValidationIndex = GetValidationIndex
MsgBox ValidationIndex
End If
End Sub
Put this function in the ThisWorkbook module also, or else in any regular module:
Function GetValidationIndex() As Long
'returns a 1-based index
Dim rngTest As Excel.Range
Dim varValidationString As Variant
Dim ErrNumber As Long
Dim i As Long
With ActiveCell.Validation
If .Type = xlValidateList Then '3
On Error Resume Next
Set rngTest = ActiveCell.Parent.Range(.Formula1)
'I do this goofy thing with ErrNumber to keep my indenting and flow pretty
ErrNumber = Err.Number
On Error GoTo 0
'if the Validation is defined as a range
If ErrNumber = 0 Then
GetValidationIndex = Application.WorksheetFunction.Match(ActiveCell.Value2, rngTest, 0)
Exit Function
'if the validation is defined by comma-separated values
Else
varValidationString = Split(.Formula1, ",")
For i = LBound(varValidationString) To UBound(varValidationString)
If varValidationString(i) = ActiveCell.Value2 Then
GetValidationIndex = i + 1
Exit Function
End If
Next i
End If
End If
End With
End Function
If you are using a list or combo box, ListIndex would seem to be what you are after.
VB Help for ListIndex property: Returns or sets the index number of the currently selected item in a list box or combo box. Read/write Long. Remarks. You cannot use this property with multiselect list boxes.
If nothing is selected, ListIndex's value is -1. If memory serves, it is a zero based index.
ListIndex cannot be set at design time so it is not listed in the properties window.
When entering your code, type the list box name then dot and the editor displays all the available properties. Scroll down the list, note any that look interesting, then look them up.
I think it is not necessary to use a function. You can get it by using only Match function, like in above Doug's answer.
Dim GetValidationIndex as Integer
Dim rngTest as Range
' Get the validation list
With ActiveCell.Validation
Set rngTest = ActiveCell.Parent.Range(.Formula1)
end with
GetValidationIndex = Application.WorksheetFunction.Match(ActiveCell.Value2, rngTest, 0)
The function GetValidationIndex is good.
However, for some regional settings the line varValidationString = Split(.Formula1, ",") is not valid, because the character used to separate the different values is ";"
I suggest use:
varValidationString = Split(.Formula1, Application.International(xlListSeparator))