How to create file hyperlink in rich text box with utf8 characters?

link 0 Reputation points
2023-04-26T12:59:04.23+00:00

I haven richtextbox and I want user can create hyperlink. so when user clicked the local file is open. first I try create project with .NET framework 4.8. in this mode the text link and URL set correctly but the link click event not firing and not detect hyperlink. The user must enter to go to a new line. Then type at least the same length of the above line. After this, by clicking on the hyperlink, the event will be executed and the file will be opened. so I tried .NET framework 4.5. now the link click event fired and file open correctly, but the link text and URL not set correctly. in other word the URL set as link text and selected text copied on from on hyper link. Also when I typed with UTF8 Character (Farsi/Arabic) the link or selected text in in both (4.8 or 4.5) turn to "?" sign. this is my code:

private void AddLinkBtn_Click(object sender, EventArgs e)
{
    string url = null;
    
    OpenFileDialog openFileDialog = new OpenFileDialog();
    //openFileDialog.Filter = "Image Files (*.jpg;*.jpeg,*.png)|*.JPG;*.JPEG;*.PNG";
    openFileDialog.Multiselect = false;
    
    if (openFileDialog.ShowDialog() == DialogResult.OK)
    {
        url = openFileDialog.FileName;
    }
    
    if (!string.IsNullOrEmpty(url))
    {
        string linkText = TextBoxDefinition.SelectedText;
        byte[] bytes = Encoding.UTF8.GetBytes(linkText);
        linkText = Encoding.UTF8.GetString(bytes);
    
        TextBoxDefinition.SelectedRtf = @"{\rtf1\utf8\field{\*\fldinst HYPERLINK ""file://" + url.Replace("\\", "/") + @""" }{\fldrslt" + linkText + "}}";
    }
}

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,736 questions
.NET
.NET
Microsoft Technologies based on the .NET software framework.
2,308 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
9,473 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Jiale Xue - MSFT 13,506 Reputation points Microsoft Vendor
    2023-04-27T06:21:55.73+00:00

    Hi @link ,

    I tested your code in .Net Framework 4.8.

    You just need to add a linkclick event to your richtextbox and it will jump the link correctly.

    private void TextBoxDefinition_LinkClicked(object sender, LinkClickedEventArgs e)
    {
        Process.Start(e.LinkText);
    }
    

    Regarding the character set not being displayed correctly, you can use the appendText method to achieve the same effect, put the entire address in double quotes so that it can be recognized correctly, and use Environment.newline to wrap the line, and use the Linkclick event to achieve your Require.

    if (!string.IsNullOrEmpty(url))
    {
        #region
        string hyperlink = @"HYPERLINK ""file://" + @url.Replace("\\", "/") + @"""";
        TextBoxDefinition.AppendText(hyperlink + Environment.NewLine);
        #endregion
    }
    

    enter image description here

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Castorix31 79,911 Reputation points
    2023-04-28T21:54:08.35+00:00

    I made a basic test with Win32 APIs and an email as link (just displayed in Console on left click) and the arabic characters are correctly displayed (.NET 4.8, Windows 10 22H2)

    (test on a Button click with a richTextBox1 control) :

        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
            public struct CHARFORMAT2
            {
                public uint cbSize;
                public uint dwMask;
                public uint dwEffects;
                public int yHeight;
                public int yOffset;
                public uint crTextColor;
                public byte bCharSet;
                public byte bPitchAndFamily;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
                public char[] szFaceName;
                public short wWeight;           // Font weight (LOGFONT value)
                public short sSpacing;         // Amount to space between letters
                public uint crBackColor;       // Background color
                public int lcid;              // Locale ID
                public uint dwReserved;           // Name up to 5.0
                //public uint dwCookie;
                public short sStyle;           // Style handle
                public short wKerning;          // Twip size above which to kern char pair
                public byte bUnderlineType;    // Underline type
                public byte bAnimation;        // Animated text like marching ants
                public byte bRevAuthor;         // Revision author index
                public byte bUnderlineColor;	// Underline color
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            public struct ENLINK
            {
                public NMHDR nmhdr;
                public uint msg;
                public IntPtr wParam;
                public IntPtr lParam;
                public CHARRANGE chrg;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct NMHDR
            {
                public IntPtr hwndFrom;
                public IntPtr idFrom;
                public int code;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct CHARRANGE
            {
                public int cpMin;
                public int cpMax;
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            public struct TEXTRANGE
            {
                public CHARRANGE chrg;
                public IntPtr lpstrText;
                //public string lpstrText;   // Allocated by caller, zero terminated by RichEdit 
            }        
    
            public const int WM_USER = 0x0400;
            public const int EM_GETCHARFORMAT = WM_USER + 58;
            public const int EM_SETCHARFORMAT = WM_USER + 68;
            public const int EM_GETTEXTRANGE = WM_USER + 75;
    
            public const int WM_NOTIFY = 0x004E;
            public const int WM_LBUTTONDOWN = 0x0201;
            public const int WM_LBUTTONUP = 0x0202;
    
            public const int EM_REPLACESEL = 0x00C2;
    
            public const int EN_LINK = 0x070b;
    
            // CHARFORMAT masks
            public const int CFM_BOLD = 0x00000001;
            public const int CFM_ITALIC = 0x00000002;
            public const int CFM_UNDERLINE = 0x00000004;
            public const int CFM_STRIKEOUT = 0x00000008;
            public const int CFM_PROTECTED = 0x00000010;
            public const int CFM_LINK = 0x00000020;         // Exchange hyperlink extension 
            public const int CFM_SIZE = unchecked((int)0x80000000);
            public const int CFM_COLOR = 0x40000000;
            public const int CFM_FACE = 0x20000000;
            public const int CFM_OFFSET = 0x10000000;
            public const int CFM_CHARSET = 0x08000000;
    
            // CHARFORMAT effects
            public const int CFE_BOLD = 0x00000001;
            public const int CFE_ITALIC = 0x00000002;
            public const int CFE_UNDERLINE = 0x00000004;
            public const int CFE_STRIKEOUT = 0x00000008;
            public const int CFE_PROTECTED = 0x00000010;
            public const int CFE_LINK = 0x00000020;
            public const int CFE_AUTOCOLOR = 0x40000000;        // NOTE: this corresponds to 
                                                                // CFM_COLOR, which controls it 
                                                                // Masks and effects defined for CHARFORMAT2 -- an (*) indicates
                                                                // that the data is stored by RichEdit 2.0/3.0, but not displayed
            public const int CFM_SMALLCAPS = 0x00000040;        // (*)	
            public const int CFM_ALLCAPS = 0x00000080;      // Displayed by 3.0 
            public const int CFM_HIDDEN = 0x00000100;       // Hidden by 3.0 
            public const int CFM_OUTLINE = 0x00000200;      // (*)	
            public const int CFM_SHADOW = 0x00000400;       // (*)	
            public const int CFM_EMBOSS = 0x00000800;       // (*)	
            public const int CFM_IMPRINT = 0x00001000;      // (*)	
            public const int CFM_DISABLED = 0x00002000;
            public const int CFM_REVISED = 0x00004000;
            public const int CFM_REVAUTHOR = 0x00008000;
            public const int CFE_SUBSCRIPT = 0x00010000;            // Superscript and subscript are 
            public const int CFE_SUPERSCRIPT = 0x00020000;          //	mutually exclusive			 
            public const int CFM_ANIMATION = 0x00040000;            // (*)	
            public const int CFM_STYLE = 0x00080000;            // (*)	
            public const int CFM_KERNING = 0x00100000;
            public const int CFM_SPACING = 0x00200000;          // Displayed by 3.0 
            public const int CFM_WEIGHT = 0x00400000;
            public const int CFM_UNDERLINETYPE = 0x00800000;            // Many displayed by 3.0 
            public const int CFM_COOKIE = 0x01000000;       // RE 6.0
            public const int CFM_LCID = 0x02000000;
            public const int CFM_BACKCOLOR = 0x04000000;            // Higher mask bits defined above
            public const int CFM_SUBSCRIPT = (CFE_SUBSCRIPT | CFE_SUPERSCRIPT);
            public const int CFM_SUPERSCRIPT = CFM_SUBSCRIPT;
    
            // CHARFORMAT "ALL" masks
            public const int CFM_EFFECTS = (CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_COLOR | 
                         CFM_STRIKEOUT | CFE_PROTECTED | CFM_LINK);
            public const int CFM_ALL = (CFM_EFFECTS | CFM_SIZE | CFM_FACE | CFM_OFFSET | CFM_CHARSET);
            public const int CFM_EFFECTS2 = (CFM_EFFECTS | CFM_DISABLED | CFM_SMALLCAPS | CFM_ALLCAPS 
    					| CFM_HIDDEN  | CFM_OUTLINE | CFM_SHADOW | CFM_EMBOSS 
    					| CFM_IMPRINT | CFM_REVISED 
    					| CFM_SUBSCRIPT | CFM_SUPERSCRIPT | CFM_BACKCOLOR);
            public const int CFM_ALL2 = (CFM_ALL | CFM_EFFECTS2 | CFM_BACKCOLOR | CFM_LCID 
    					| CFM_UNDERLINETYPE | CFM_WEIGHT | CFM_REVAUTHOR 
    					| CFM_SPACING | CFM_KERNING | CFM_STYLE | CFM_ANIMATION 
    					| CFM_COOKIE);
            public const int CFE_SMALLCAPS = CFM_SMALLCAPS;
            public const int CFE_ALLCAPS = CFM_ALLCAPS;
            public const int CFE_HIDDEN = CFM_HIDDEN;
            public const int CFE_OUTLINE = CFM_OUTLINE;
            public const int CFE_SHADOW = CFM_SHADOW;
            public const int CFE_EMBOSS = CFM_EMBOSS;
            public const int CFE_IMPRINT = CFM_IMPRINT;
            public const int CFE_DISABLED = CFM_DISABLED;
            public const int CFE_REVISED = CFM_REVISED;
    
            // CFE_AUTOCOLOR and CFE_AUTOBACKCOLOR correspond to CFM_COLOR and
            // CFM_BACKCOLOR, respectively, which control them
            public const int CFE_AUTOBACKCOLOR = CFM_BACKCOLOR;
            public const int CFM_FONTBOUND = 0x00100000;
            public const int CFM_LINKPROTECTED = 0x00800000;    // Word hyperlink field
            public const int CFM_EXTENDED = 0x02000000;
            public const int CFM_MATHNOBUILDUP = 0x08000000;
            public const int CFM_MATH = 0x10000000;
            public const int CFM_MATHORDINARY = 0x20000000;
            public const int CFM_ALLEFFECTS = (CFM_EFFECTS2 | CFM_FONTBOUND | CFM_EXTENDED | CFM_MATHNOBUILDUP | CFM_MATH | CFM_MATHORDINARY);
            public const int CFE_FONTBOUND = 0x00100000;    // Font chosen by binder, not user;
            public const int CFE_LINKPROTECTED = 0x00800000;
            public const int CFE_EXTENDED = 0x02000000;
            public const int CFE_MATHNOBUILDUP = 0x08000000;
            public const int CFE_MATH = 0x10000000;
            public const int CFE_MATHORDINARY = 0x20000000;
    
            // Underline types. RE 1.0 displays only CFU_UNDERLINE
            public const int CFU_CF1UNDERLINE = 0xFF;   // Map charformat's bit underline to CF2
            public const int CFU_INVERT = 0xFE; // For IME composition fake a selection
            public const int CFU_UNDERLINETHICKLONGDASH = 18;   // (*) display as dash
            public const int CFU_UNDERLINETHICKDOTTED = 17; // (*) display as dot
            public const int CFU_UNDERLINETHICKDASHDOTDOT = 16; // (*) display as dash dot dot
            public const int CFU_UNDERLINETHICKDASHDOT = 15;    // (*) display as dash dot
            public const int CFU_UNDERLINETHICKDASH = 14;   // (*) display as dash
            public const int CFU_UNDERLINELONGDASH = 13;    // (*) display as dash
            public const int CFU_UNDERLINEHEAVYWAVE = 12;   // (*) display as wave
            public const int CFU_UNDERLINEDOUBLEWAVE = 11;  // (*) display as wave
            public const int CFU_UNDERLINEHAIRLINE = 10;    // (*) display as single	
            public const int CFU_UNDERLINETHICK = 9;
            public const int CFU_UNDERLINEWAVE = 8;
            public const int CFU_UNDERLINEDASHDOTDOT = 7;
            public const int CFU_UNDERLINEDASHDOT = 6;
            public const int CFU_UNDERLINEDASH = 5;
            public const int CFU_UNDERLINEDOTTED = 4;
            public const int CFU_UNDERLINEDOUBLE = 3;// (*) display as single
            public const int CFU_UNDERLINEWORD = 2;// (*) display as single	
            public const int CFU_UNDERLINE = 1;
            public const int CFU_UNDERLINENONE = 0;
    
            // EM_SETCHARFORMAT wParam masks 
            public const int SCF_SELECTION = 0x0001;
            public const int SCF_WORD = 0x0002;
            public const int SCF_DEFAULT = 0x0000;  // Set default charformat or paraformat
            public const int SCF_ALL = 0x0004;  // Not valid with SCF_SELECTION or SCF_WORD
            public const int SCF_USEUIRULES = 0x0008;   // Modifier for SCF_SELECTION; says that
                                                        //	format came from a toolbar, etc., and
                                                        //	hence UI formatting rules should be
                                                        //	used instead of literal formatting
            public const int SCF_ASSOCIATEFONT = 0x0010;    // Associate fontname with bCharSet (one
                                                            //	possible for each of Western, ME, FE,
                                                            //	Thai)
            public const int SCF_NOKBUPDATE = 0x0020;   // Do not update KB layout for this change
                                                        //	even if autokeyboard is on
            public const int SCF_ASSOCIATEFONT2 = 0x0040;   // Associate plane-2 (surrogate) font
            public const int SCF_SMARTFONT = 0x0080;    // Apply font only if it can handle script (5.0)
            public const int SCF_CHARREPFROMLCID = 0x0100;  // Get character repertoire from lcid (5.0)
    
            public const int SPF_DONTSETDEFAULT = 0x0002;   // Suppress setting default on empty control
            public const int SPF_SETDEFAULT = 0x0004;   // Set the default paraformat
    
            [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                AppendText(richTextBox1.Handle, "This is a test" + Environment.NewLine, (uint)ColorTranslator.ToWin32(System.Drawing.Color.Red), CFE_BOLD);
                string sText = "{\\rtf1 Test {\\field{\\*\\fldinst{HYPERLINK \"mailto:test@test.com\"}}{\\fldrslt{\\cf1\\ul email  من برنامه نویس هست}}}}";
                AppendText(richTextBox1.Handle, sText, (uint)ColorTranslator.ToWin32(System.Drawing.Color.Orange), CFE_LINK);
            }
    
            public void AppendText(IntPtr hWnd, string sText, uint nColor, uint nEffect)
            {
                CHARFORMAT2 cfmt2 = new CHARFORMAT2();
                cfmt2.cbSize = (uint)Marshal.SizeOf(cfmt2); //116
                cfmt2.dwMask = CFM_COLOR | CFM_LINK | CFM_BOLD | CFM_STRIKEOUT;
                cfmt2.crTextColor = nColor;
                cfmt2.dwEffects = nEffect;
               
                IntPtr plParam = Marshal.AllocHGlobal(Marshal.SizeOf(cfmt2));
                Marshal.StructureToPtr(cfmt2, plParam, false);
                int nRet = SendMessage(hWnd, EM_SETCHARFORMAT, (IntPtr)SCF_SELECTION, plParam);           
                Marshal.FreeHGlobal(plParam);
    
                IntPtr pString = Marshal.StringToHGlobalUni(sText);
                nRet = SendMessage(hWnd, EM_REPLACESEL, IntPtr.Zero, pString);
            }
    
            protected override void WndProc(ref Message m)
            {
                base.WndProc(ref m);
                if (m.Msg == WM_NOTIFY)
                {
                    var pNMHDR = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
                    if (pNMHDR.code == EN_LINK)
                    {
                        var pENLINK = (ENLINK)Marshal.PtrToStructure(m.LParam, typeof(ENLINK));
                        //Console.WriteLine(String.Format("0x{0:X4}", pENLINK.msg));
                        if (WM_LBUTTONDOWN == pENLINK.msg)
                        {
                            TEXTRANGE tr = new TEXTRANGE();
                            tr.chrg = pENLINK.chrg;
                            IntPtr hGlobal = Marshal.AllocHGlobal(260);                       
                            tr.lpstrText = hGlobal;
                            IntPtr plParam = Marshal.AllocHGlobal(Marshal.SizeOf(tr));
                            Marshal.StructureToPtr(tr, plParam, false);
                            int nRet = SendMessage(richTextBox1.Handle, EM_GETTEXTRANGE, IntPtr.Zero, plParam);
                            int nErr = Marshal.GetLastWin32Error();
                            TEXTRANGE trRet = (TEXTRANGE)Marshal.PtrToStructure(plParam, typeof(TEXTRANGE));
                            string sText = Marshal.PtrToStringUni(hGlobal);
                            Console.WriteLine(sText);
                            Marshal.FreeHGlobal(hGlobal);
                            Marshal.FreeHGlobal(plParam);
                        }
                    }
                }
            }
        }
    
    0 comments No comments