1

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
HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
darbid
  • 2,545
  • 23
  • 55
  • Take a look at this question [How to get a word under cursor using JavaScript?](http://stackoverflow.com/questions/2444430/how-to-get-a-word-under-cursor-using-javascript) that have some good answers. You can use one of those javascript answers and It's enough to inject the script into page programmatically. You can also rewrite the logic in C# and bind to events using C# code and web browser control api. I used this search: javascript get word under caret – Reza Aghaei Jan 20 '16 at 22:23
  • Thanks for the comment. I am not an expert in JavaScript but it appears all the answers rely on a mouse over event which they add, in some cases they first add a span to each word and add an event listener to each of these tags. I cannot use events all the time. – darbid Jan 21 '16 at 05:42
  • You are welcome, The main idea is wrapping all word in a span, then instead of mouse over, you can use click event. – Reza Aghaei Jan 21 '16 at 09:34
  • Yes and that is a good answer if I always had an event listener set. In fact the event gives you the element and a position so it would be easy to work out where exactly the person clicked. Unfortunately I do not have access to these events all the time. – darbid Jan 21 '16 at 13:52
  • You have access to those events. You can inject any script including a jquery method to do that. – Reza Aghaei Jan 21 '16 at 14:32

1 Answers1

0

I cannot post answer with a nice function that shows how to do this but I will explain the important parts.

  1. user the Win32 API GetCursorPos to get the point on the screen where the user last clicked.
  2. 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.
  3. Convert this point to a relative point using the IHTMLWindow you found in number 2.
  4. Once you have the right HTMLDocument and a point relative to this document you can then use the elementFromPoint method on a IHTMLDocument2 object.
  5. 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
    
darbid
  • 2,545
  • 23
  • 55