4

I need to suppress some linebreaks in a RichTextBox.

For example, consider d6+. There must not be a line break between 6 and +. Basically I'm looking for something like <nobr> in HTML.

So far, I've messed around with inserting \u+FEFF etc (which worked on some machines, but some showed vertical lines, maybe a font problem although windows standard font). I also tried to manipulate the rtf directly, i.e. box.rtf = ... with putting some \zwnbo in there, but I never seem to get it right.

Help much appreciated.

stuartd
  • 70,509
  • 14
  • 132
  • 163
Eiko
  • 25,601
  • 15
  • 56
  • 71

4 Answers4

5

Yes, you can use all of the RichText API with your RichTextBox control.

You may be interested take a look at the following sites:

http://www.pinvoke.net/default.aspx/user32.sendmessage - how to send messages to windows using p/invoke.

You can use the Handle property of the RichTextBox to get the window handle of that control, and then send messages to it.

Also look at these include files shiped with SDK from Microsoft, thaty are not going to be used directly in C#, but these files constains all constants you may have to use, such as WB_ISDELIMITER, WB_CLASSIFY and others.

  • winuser.h
  • richedit.h

In the following example I demonstrate how to use the APIs provided.

EDIT:

This new sample has code marked as unsafe, but it is better because it does not suffer from the problem of single character string, since I can have a char* parameter and manipulate it. The old sample follows this one:

This is C# code, not C++... to compile it you will have to go to project options and mark the check-box to allow unsafe code to run.

Right click the project -> Properties (Alt+Enter) -> Build -> General -> Allow unsafe code (must be checked)

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace q6359774
{
    class MyRichTextBox : RichTextBox
    {
        const int EM_SETWORDBREAKPROC = 0x00D0;
        const int EM_GETWORDBREAKPROC = 0x00D1;

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            this.Text = "abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz";
            NewMethod();
        }

        unsafe private void NewMethod()
        {
            if (!this.DesignMode)
                SendMessage(this.Handle, EM_SETWORDBREAKPROC, IntPtr.Zero, Marshal.GetFunctionPointerForDelegate(new EditWordBreakProc(MyEditWordBreakProc)));
        }

        [DllImport("User32.DLL")]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        unsafe delegate int EditWordBreakProc(char* lpch, int ichCurrent, int cch, int code);

        unsafe int MyEditWordBreakProc(char* lpch, int ichCurrent, int cch, int code)
        {
            const int WB_ISDELIMITER = 2;
            const int WB_CLASSIFY = 3;
            if (code == WB_ISDELIMITER)
            {
                char ch = *lpch;
                return ch == '-' ? 0 : 1;
            }
            else if (code == WB_CLASSIFY)
            {
                char ch = *lpch;
                var vResult = Char.GetUnicodeCategory(ch);
                return (int)vResult;
            }
            else
            {
                var lpch2 = lpch;
                // in this case, we must find the begining of a word:
                for (int it = ichCurrent; it < cch; it++)
                {
                    char ch = *lpch2;
                    if (it + 1 < cch && lpch2[0] == '-' && lpch2[1] != '-')
                        return it;
                    if (lpch2[0] == '\0')
                        return 0;
                    lpch2++;
                }
            }

            return 0;
        }
    }
}

Old Sample Code

The sample consists of a class that inherits from RichTextBox and places a custom handler using EM_SETWORDBREAKPROC. This class will only break lines exactly when over '-' character. Not before, nor after.

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace q6359774
{
    class MyRichTextBox : RichTextBox
    {
        const int EM_SETWORDBREAKPROC = 0x00D0;
        const int EM_GETWORDBREAKPROC = 0x00D1;

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            this.Text = "abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz";
            if (!this.DesignMode)
                SendMessage(this.Handle, EM_SETWORDBREAKPROC, IntPtr.Zero, Marshal.GetFunctionPointerForDelegate(new EditWordBreakProc(MyEditWordBreakProc)));
        }

        [DllImport("User32.DLL")]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        delegate int EditWordBreakProc(string lpch, int ichCurrent, int cch, int code);

        int MyEditWordBreakProc(string lpch, int ichCurrent, int cch, int code)
        {
            const int WB_ISDELIMITER = 2;
            const int WB_CLASSIFY = 3;
            if (code == WB_ISDELIMITER)
            {
                if (lpch.Length == 0 || lpch == null) return 0;
                char ch = lpch[ichCurrent];
                return ch == '-' ? 0 : 1;
            }
            else if (code == WB_CLASSIFY)
            {
                if (lpch.Length == 0 || lpch == null) return 0;
                char ch = lpch[ichCurrent];
                var vResult = Char.GetUnicodeCategory(ch);
                return (int)vResult;
            }
            else
            {
                if (lpch.Length == 0 || lpch == null) return 0;
                for (int it = ichCurrent; it < lpch.Length; it++)
                {
                    char ch = lpch[it];
                    if (ch != '-') return it;
                }
            }

            return 0;
        }
    }
}

It is just a draft, so you may have to further improve it, so you can achieve your goals.

Place the control in a windows form, and run.

Resize the window and see if this is what you want to do!

You will have to make the seeking of word boundaries... I didn't manage to make it work yet.

Miguel Angelo
  • 23,796
  • 16
  • 59
  • 82
  • I am currently experimenting with it. The method is called at strange times/positions, though. Also, lpch does only contain one character. Maybe it's an encoding thing? – Eiko Jun 21 '11 at 11:12
  • @Eiko: that happens because the type lpch of `EditWordBreakProc` is a pointer to a character (i.e. `char*` in c++), but the marshaller understands it as being a pointer to a sigle character string wich is not true, it is a pointer to a character in the middle of the text-buffer of the RichTextBox... it is not an encoding problem. – Miguel Angelo Jun 22 '11 at 00:36
  • Maybe I get the variables wrong, but I've set up a text box with "aaaaaaaa bbbbbbbbb cccc" in it, and the method gets called with ichCurrent = 15 and cch = 32 (which is longer than the string itself). Break should happen somewhere between b and c here. – Eiko Jun 22 '11 at 12:09
  • I think I got it working with a couple of modifications. I'll put up my solution as a separate answer later and award you the bounty. – Eiko Jun 22 '11 at 13:10
  • Just posted the answer. Some modifications were needed, i.e. storing the delegate method to preserve garbage collection, and walking left/right correctly to identify the break positions. It was a good starting point, though. Thanks. – Eiko Jun 22 '11 at 16:34
2

I'm not sure, but if RichTextBox is actually wrapping a Windows rich edit control, you might have some luck by reading this:

http://msdn.microsoft.com/en-us/library/hh270412%28v=vs.85%29.aspx

Or more specifically this:

http://msdn.microsoft.com/en-us/library/bb787877%28v=vs.85%29.aspx

I hope this helps.

fritten
  • 111
  • 1
  • 6
1

Here's my solution (based on @Miguel Angelo, but modified and corrected a bit):

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
    public class SpaceBreakingRichTextBox : RichTextBox
    {
        const int EM_SETWORDBREAKPROC = 0x00D0;
        const int EM_GETWORDBREAKPROC = 0x00D1;

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            AddDelegate();
        }

        [DllImport("User32.DLL")]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        unsafe delegate int EditWordBreakProc(char* lpch, int ichCurrent, int cch, int code);
        EditWordBreakProc myDelegate;

        unsafe private void AddDelegate()
        {
            if (!this.DesignMode)
            {
                myDelegate = new EditWordBreakProc(MyEditWordBreakProc);
                SendMessage(this.Handle, EM_SETWORDBREAKPROC, IntPtr.Zero, Marshal.GetFunctionPointerForDelegate(myDelegate));
            }
        }

        unsafe int MyEditWordBreakProc(char* lpch, int ichCurrent, int cch, int code)
        {
            const int WB_ISDELIMITER = 2;
            const int WB_CLASSIFY = 3;
            const int WB_MOVEWORDLEFT = 4;
            const int WB_MOVEWORDRIGHT = 5;

            const int WB_LEFTBREAK = 6;
            const int WB_RIGHTBREAK = 7;

            const int WB_LEFT = 0;
            const int WB_RIGHT = 1;

            if (code == WB_ISDELIMITER)
            {
                char ch = *lpch;
                return ch == ' ' ? 1 : 0;
            }
            else if (code == WB_CLASSIFY)
            {
                char ch = *lpch;
                var vResult = Char.GetUnicodeCategory(ch);
                return (int)vResult;
            }
            else if (code == WB_LEFTBREAK)
            {
                for (int it = ichCurrent; it >= 0; it--)
                {
                    if (lpch[it] == ' '/* && lpch2[1] != ' '*/)
                    {
                        if (it > 0 && lpch[it - 1] != ' ')
                            return it;
                    }
                }
            }
            else if (code == WB_RIGHT)
            {
                for (int it = ichCurrent; ; it++)
                {
                    if (lpch[it] != ' ')
                        return it;
                }
            }
            else
            {
                 // There might be more cases to handle (see constants)
            }
            return 0;
        }
    }
}

Note that you need to keep the delegate method around, or it will crash as it gets collected from the garbage collector (which was a pain to debug).

Basically, this subclass only breaks at spaces, which is good enough for my needs at the moment.

Eiko
  • 25,601
  • 15
  • 56
  • 71
0

I've quickly tried this, it seems to work:

this.userControl.richTextBox1.LoadFile("C:\\test.rtf");
this.userControl.richTextBox1.Rtf = this.userControl.richTextBox1.Rtf.Replace(@"\par", String.Empty);

this.userControl.richTextBox1.SaveFile("C:\\test2.rtf", RichTextBoxStreamType.RichText);
Vince
  • 1,036
  • 1
  • 10
  • 17
  • I don't want to remove all paragraphs. I need to suppress line breaks at *certain* positions. – Eiko Jun 22 '11 at 08:32
  • Yes it is what I understood but I don't know how you define such data. If you know exactly the text you want to change just do a Find before and then Replace from the Find result on the length of text + \par, no? – Vince Jun 22 '11 at 12:15
  • I just set the text of some RichtTextBox. You might even enter it in the box directly. There are no explicit linebreaks. – Eiko Jun 22 '11 at 12:25
  • What i don't understand is when you say that you want to suppress line breaks at certain positions, what defines "certain"? – Vince Jun 22 '11 at 12:52
  • See my example in question. I must not break within "d6+" or "e2-e4". It's as simple as protecting certain regions from breaking. It seems that allowing to break only at spaces will do the job, though. – Eiko Jun 22 '11 at 13:09