Click here to Skip to main content
15,899,026 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
After having solved with the margin Get the left margin of a richtextbox[^] I have another question which also seems shrouded in mystery, in practically nothing exists on the internet, yet it seems trivial, like finding out when the mouse pointer is over an image in a richtextbox. I've tried practically everything, even with the Windows API, but there's no way to succeed.

What I have tried:

Below is my last failed attempt:

VB
Private Function IsMouseOverImage(ByVal mousePosition As Point) As Boolean
        Dim position As Integer = GetCharIndexFromPosition(mousePosition)
        If position >= 0 Then
            Dim text As String = Me.Rtf
            Dim imageIndex As Integer = Rtf.IndexOf(ChrW(&HFFFC))
            While imageIndex <> -1
                Dim imagePosition As Integer = Me.GetFirstCharIndexFromLine(Me.GetLineFromCharIndex(imageIndex))
                Dim imageEndPosition As Integer = Me.GetFirstCharIndexFromLine(Me.GetLineFromCharIndex(imageIndex) + 1)
                If imageEndPosition = -1 Then
                    imageEndPosition = Me.Rtf.Length
                End If

                Dim startCharBounds As Rectangle = GetCharacterBounds(imagePosition)
                Dim endCharBounds As Rectangle = GetCharacterBounds(imageEndPosition - 1)

                Dim startRectangle As New Rectangle(startCharBounds.Left, startCharBounds.Top, startCharBounds.Width, startCharBounds.Height)
                Dim endRectangle As New Rectangle(endCharBounds.Left, endCharBounds.Top, endCharBounds.Width, endCharBounds.Height)

                startRectangle = Me.RectangleToScreen(startRectangle)
                endRectangle = Me.RectangleToScreen(endRectangle)

                Dim imageRect As New Rectangle(startRectangle.Left, startRectangle.Top, endRectangle.Right - startRectangle.Left, endRectangle.Bottom - startRectangle.Top)
                If imageRect.Contains(mousePosition) Then
                    Return True
                End If

                imageIndex = Rtf.IndexOf(ChrW(&HFFFC), imageIndex + 1)
            End While
        End If

        Return False
    End Function

VB
Private Function GetCharacterBounds(ByVal index As Integer) As Rectangle
        Dim charPosition As Point = Me.GetPositionFromCharIndex(index)
        Dim charSize As Size = TextRenderer.MeasureText(Me.Text(index), Me.Font)
        Return New Rectangle(charPosition, charSize)
    End Function

Thanks and sorry for the Google English...
Posted
Updated 29-Apr-24 2:18am
v2

Answer updated because the proposed method doesn't work. The 'why' of it not working is explained here for reference.

The RichTextBox control has two properties of interest, .Text and .Rtf.

I created a test document with two paragraphs of Lorem Ipsum and an image in-between.
The .Length of the .Text property is 1231 characters.
The .Length of the .Rtf property is 525,357 characters.

The .Rtf property includes everything, all the escape codes and the image itself in hex format.
The .Text property includes only the two paragraphs of text and a about 2-3 characters which must be some kind of placeholder for the image.

The following code uses the GetCharIndexFromPosition method of the RichTextBox control, the value returned (for my test document) ranges from 0 to 1230. You will notice that 1230 is very similar to the .Length property of the RichTextBox.Text control property. I.e. GetCharIndexFromPosition is giving a position within the text represented by the .Text property (or some very similar property).
VB
Private Sub RichTextBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles RichTextBox1.MouseMove
    Dim position As Integer = RichTextBox1.GetCharIndexFromPosition(e.Location)

The code we use to find the location of the images in the document is shown further below. It uses the .Rtf property as we can see in this example :
VB
Dim currentImageStart As Integer = RichTextBox1.Rtf.IndexOf(startTag, 0)

However, and here's the problem, analysing the .Rtf property gives a start and end position (in my test document) of 924 and 525,357 respectively whereas GetCharIndexFromPosition gives values of 597 and 598 whilst hovering the mouse over different parts of the image.
Hence checking to see if 597 or 598 are in the range 924 to 525,357 is never going to work.


Here's the full method to get the start,end position pairs for all images in the doucement using "{\pict" to define where the image starts (please do not throw exceptions the way I have, this was just to thrown together to find out what was going on) :
VB
Private imageLocations As List(Of Tuple(Of Integer, Integer))
    
Private Function FindImageLocations() As Boolean
    imageLocations.Clear()

    Dim startTag As String = "{\pict"
    Dim openBrace As Char = "{"c
    Dim closeBrace As Char = "}"c
    Dim braceCount As Integer
    Dim currentPosition As Integer
    Dim nextOpenBrace As Integer
    Dim nextCloseBrace As Integer

    Dim currentImageStart As Integer = RichTextBox1.Rtf.IndexOf(startTag, 0)
    If currentImageStart = -1 Then Return False

    While currentImageStart <> -1
        braceCount = 1
        currentPosition = currentImageStart + startTag.Length
        While braceCount <> 0
            nextOpenBrace = RichTextBox1.Rtf.IndexOf(openBrace, currentPosition)
            nextCloseBrace = RichTextBox1.Rtf.IndexOf(closeBrace, currentPosition)
            If nextOpenBrace <> -1 Then
                currentPosition = Math.Min(nextOpenBrace, nextCloseBrace)
            Else
                currentPosition = nextCloseBrace
            End If
            If RichTextBox1.Rtf.Chars(currentPosition) = openBrace Then
                braceCount += 1
            ElseIf RichTextBox1.Rtf.Chars(currentPosition) = closeBrace Then
                braceCount -= 1
            Else
                Throw New Exception("Unexpected error")
            End If
            currentPosition += 1
        End While
        imageLocations.Add(Tuple.Create(currentImageStart, currentPosition))
        ToolStripStatusLabel2.Text = "Start, " & currentImageStart.ToString()
        ToolStripStatusLabel3.Text = "End, " & currentPosition.ToString()
        currentImageStart = RichTextBox1.Rtf.IndexOf(startTag, currentPosition)
    End While
    Return True
End Function
 
Share this answer
 
v2
Comments
10071962 29-Apr-24 8:59am    
Thank you, but I had tried, also replacing Dim imageIndex As Integer = text.IndexOf("\pict\wmetafile8"), the RTF code is "{\rtf1\ansi\ansicpg1252\deff0\deflang1040{\fonttbl{\f0\ fnil\fcharset0 Microsoft Sans Serif;}}
\viewkind4\uc1\pard\f0\fs17\par
{\pict\wmetafile8\picw3386\pich3386\picwgoal1920\pichgoal1920
010009000003386000000000226000000000050000000b0200000000050000000c023a0d3a0d22" etc... The nice thing is that I created a new project, with an empty derived class but it doesn't want to know about it..
M-Badger 4-May-24 13:17pm    
OK, the reason that this does not work is that GetCharIndexFromPosition gets the index of the character under the mouse pointer from a version of the document text where the image is represented by only 2 or 3 characters whereas the method for finding the location of the image uses the .Rtf property which includes the entire image in hex format. I haven't found a solution I'm afraid.
I'll update the answer.
10071962 5-May-24 2:54am    
First of all, I thank you for the time you spent and the effort you made, I attach the new derived class, completely empty, that I created to test your code, avoiding possible interference, and I inserted it in a form that is also empty , the problem is that it doesn't work for me, the hand appears randomly in the control area even though the mouse coordinates are correct and the image inserted correctly in the "imageLocations", I don't want to have done something wrong. I also tried with the Windows API (IRichEditOle Interface) but it doesn't work, yet for a relatively simple thing there is no direct call to the RichtextBox which is unnerving. Thanks again and happy Sunday. NOTE: I attach the class as an answer, I can't do it here, maybe the text is too long.
M-Badger 5-May-24 4:57am    
Look back at the first line of my updated answer - the code provided DOES NOT WORK - I have explained why. I have not found an alternative method using the built-in RichTextBox control. You might try 3rd party RichTextBox controls to see if they provide the functionality that you want.
10071962 5-May-24 7:54am    
You're right, sorry, I'll keep looking... Thanks!
Option Explicit On
Option Strict Off

Imports System.Windows

Public Class ExtendedRichTextBox : Inherits RichTextBox

    Private imageLocations As List(Of Tuple(Of Integer, Integer))

    Public Sub New()
        MyBase.New()
        imageLocations = New List(Of Tuple(Of Integer, Integer))
    End Sub

    '*****************************************************************************************
    '*****************************************************************************************

    Public Sub Me_InsertImage(ImagePath As String)

        If Not System.IO.File.Exists(ImagePath) Then
            Dim parentForm As Form = Me.FindForm()
            Forms.MessageBox.Show(parentForm, "Il percorso dell'immagine specificato non esiste.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        Dim img As Image = Image.FromFile(ImagePath)

        Forms.Clipboard.SetImage(img)
        Me.Paste()

    End Sub

    '*****************************************************************************************
    '*****************************************************************************************

    Protected Overrides Sub OnTextChanged(e As EventArgs)
        MyBase.OnTextChanged(e)
        If Me.IsHandleCreated Then
            FindImageLocations()
        End If
    End Sub

    Private Function FindImageLocations() As Boolean
        imageLocations.Clear()

        Dim startTag As String = "{\pict"
        Dim openBrace As Char = "{"c
        Dim closeBrace As Char = "}"c
        Dim braceCount As Integer
        Dim currentPosition As Integer
        Dim nextOpenBrace As Integer
        Dim nextCloseBrace As Integer

        Dim currentImageStart As Integer = Me.Rtf.IndexOf(startTag, 0)
        If currentImageStart = -1 Then Return False

        While currentImageStart <> -1
            braceCount = 1
            currentPosition = currentImageStart + startTag.Length
            While braceCount <> 0
                nextOpenBrace = Me.Rtf.IndexOf(openBrace, currentPosition)
                nextCloseBrace = Me.Rtf.IndexOf(closeBrace, currentPosition)
                If nextOpenBrace <> -1 Then
                    currentPosition = Math.Min(nextOpenBrace, nextCloseBrace)
                Else
                    currentPosition = nextCloseBrace
                End If
                If Me.Rtf.Chars(currentPosition) = openBrace Then
                    braceCount += 1
                ElseIf Me.Rtf.Chars(currentPosition) = closeBrace Then
                    braceCount -= 1
                Else
                    Throw New Exception("Unexpected error")
                End If
                currentPosition += 1
            End While
            imageLocations.Add(Tuple.Create(currentImageStart, currentPosition))
            My.Forms.Form_Di_Test_09_ExtendedRichTextBox.ToolStripStatusLabel_Start.Text = "Start, " & currentImageStart.ToString()
            My.Forms.Form_Di_Test_09_ExtendedRichTextBox.ToolStripStatusLabel_End.Text = "End, " & currentPosition.ToString()
            currentImageStart = Me.Rtf.IndexOf(startTag, currentPosition)
        End While
        Return True
    End Function

    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)

        If m.Msg = WM_SETCURSOR Then

            Dim clientCursorPos As Drawing.Point = PointToClient(Control.MousePosition)

            Debug.WriteLine("WndProc - clientCursorPos: " & clientCursorPos.ToString)
            Debug.WriteLine("WndProc - IsMouseOverImage: " & IsMouseOverImage(clientCursorPos))

            If IsMouseOverImage(clientCursorPos) Then
                Cursor.Current = Cursors.Hand
                m.Result = New IntPtr(1)
            Else
                Cursor.Current = Cursors.IBeam
            End If
        End If

    End Sub

    Private Function IsMouseOverImage(mouseLocation As Drawing.Point) As Boolean
        For Each _Location In imageLocations

            Debug.WriteLine("IsMouseOverImage - imageLocations.Count: " & imageLocations.Count)
            Debug.WriteLine("IsMouseOverImage - mouseLocation: " & mouseLocation.ToString)
            Debug.WriteLine("IsMouseOverImage - _Location.Item1: " & _Location.Item1)
            Debug.WriteLine("IsMouseOverImage - _Location.Item2: " & _Location.Item2)

            Dim start As Integer = _Location.Item1
            Dim [end] As Integer = _Location.Item2
            If mouseLocation.X >= start AndAlso mouseLocation.X <= [end] Then
                Return True
            End If
        Next
        Return False
    End Function

End Class
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900