Click here to Skip to main content
13,258,479 members (50,864 online)
Click here to Skip to main content
Add your own
alternative version

Stats

7.8K views
946 downloads
24 bookmarked
Posted 13 Jun 2017

A Full Featured Editor Based On The Richtext Box

, 18 Jun 2017
Rate this:
Please Sign up or sign in to vote.
Creating a reusable editor for C# Applications using the RichTextBox Control

Introduction

The RichTextBox control (RTB) in the NET framework offers a simple way to edit richtext markup code, used in rtf files and older MS-Word documents, as well as simple text, in a WYSIWYG manner. However,  in its basic form it lacks the features of a more complete editor, such as Find and Replace, document printing, page layout, and file and image drag and drop.

Using the code

This project began as an effort to make the base RichTextBox class into a more full featured editor that could serve as a reusable component for other applications I was developing. While the RTB has many useful properties and methods, it is not a full featured editor in its basic form. In the course of adding what I felt were necessary improvements, the following were the main problems I encountered, along with their solutions from many different sources on the net:

1. Printing - There is no built-in way to print the contents of an RTB. I created a PrintHelper class with a GeneralPrintForm constructor, available as a separate "Tips & Tricks" post here: https://www.codeproject.com/Tips/829368/A-Simple-RTF-Print-Form". Note that the Net PrintDialog also provides a PrintPreview window which shows how your document will print. The PrintHelper class uses the default Print and Preview Dialogs.

2. Page Layout and Margins - A relatively simple problem with the appearance of an RTB on a form is the absence of a visual margin for its contents. The easy way to solve this is enclose RTB in a slightly larger Panel container with a FixedSingle BorderStyle, so there is an apparent margin between the scrolling text in the control and the surrounding edges of the panel. Note that this only simulates the margins of the printed page. To set the actual Margins, Header and Footer for the printed page, using the GeneralPrintForm from the PrintHelper Class in Item 1 above, a System.Drawing.Printing.PageSettings object is filled in and sent to the GeneralPrintForm via its constructor:

// PRINT MENU OPTION
       private void printToolStripMenuItem_Click(object sender, EventArgs e)
       {
           string finalheader = HeaderString;

           Exception ex = new Exception("An Error occurred while Printing");
           System.Drawing.Printing.PageSettings PSS = new System.Drawing.Printing.PageSettings();
           PSS.Margins.Left = (int)(LeftMargin * 100);
           PSS.Margins.Right = (int)(RightMargin * 100);
           PSS.Margins.Top = (int)(TopMargin * 100);
           PSS.Margins.Bottom = (int)(BottomMargin * 100);
           PSS.Landscape = LandScapeModeOn;
           if (HeaderOn)
           {
               if(AddFileNameToHeader)
               {
                   finalheader += " " + FileName;
               }
               if(AddDateToHeader)
               {
                   finalheader += " Printed: " + System.DateTime.Today.ToShortDateString() + " " + System.DateTime.Now.ToShortTimeString();
               }

           }

           rtt.GeneralPrintForm("Print Document", rtbMainForm1.Rtf, ref ex, PSS, HeaderOn, finalheader, HeaderFont, HeaderNumberPages);

       }

I created a simple form to collect these settings from the user within the editor:

 public partial class marginsForm : Form
    {
        public marginsForm()
        {
            InitializeComponent();
        }
        // OVERRIDE 1
        public marginsForm(float Top,float Bottom, float Left, float Right,bool landscapemodeon)
        {
            InitializeComponent();
            top = Top;
            bottom = Bottom;
            left = Left;
            right = Right;
            landscapemode = landscapemodeon;

        }
        // OVERRIDE 2
        public marginsForm(float Top, float Bottom, float Left, float Right, bool landscapemodeon,string headerstring,Font headerfont,bool headeron,bool numberpages,
           bool adddatetoheader, bool addfilenametoheader)
        {
            InitializeComponent();
            top = Top;
            bottom = Bottom;
            left = Left;
            right = Right;
            landscapemode = landscapemodeon;
            this.headerfont = headerfont;
            this.headeron = headeron;
            this.pagenumberson = numberpages;
            this.headerstring = headerstring;
            this.adddatetoheader = adddatetoheader;
            this.addfilenametoheader = addfilenametoheader;

        }

        // PUBLIC ACCESSORS
        public bool ResultOk
        {
            get
            {
                return resultok;
            }
           

        }
        public float Top
        {
            get
            {
                return top;
            }
            set
            {
                top = value;
            }
        }
        public float Bottom
        {
            get
            {
                return bottom;
            }
            set
            {
                bottom = value;
            }
        }
        public float Left
        {
            get
            {
                return left;
            }
            set
            {
                left = value;
            }
        }
        public float Right
        {
            get
            {
                return right;
            }
            set
            {
                right = value;
            }
        }
        public bool LandscapeMode
        {
            get
            {
                return landscapemode;
            }
            set
            {
                landscapemode = value;
            }


        }
        public Font HeaderFont
        {
            get
            {
                return headerfont;
            }
        }
        public bool HeaderOn
        {
            get
            {
                return headeron;
            }
            set
            {
                headeron = value;
            }
        }
        public bool PageNumbersOn
        {
            get
            {
                return pagenumberson;
            }
            set
            {
                pagenumberson = value;
            }
        }
        public string DocumentFilename
        {
            get
            {
                return documentfilename;
            }
            set
            {
                documentfilename = value;
            }
        }
        public string HeaderString
        {
            get
            {
                return headerstring;
            }
            set
            {
                headerstring = value;
            }
        }
        public bool AddDateToHeader
        {
            get
            {
                return adddatetoheader;
            }
            set
            {
                adddatetoheader = value;
            }
        }
        public bool AddFileNameToHeader
        {
            get
            {
                return addfilenametoheader;
            }
            set
            {
                addfilenametoheader = value;
            }
        }
        
       
        // PRIVATE VARIABLES
        private float top, bottom, left, right = 0.0f;
        private bool landscapemode = false;
        private Font headerfont = new Font("Arial", 10); // DEFAULT
        private bool headeron = false;
        private bool pagenumberson = false;
        private bool adddatetoheader = false;
        private bool addfilenametoheader = false;
        private string documentfilename = string.Empty;
        private string headerstring = string.Empty;
        ExceptionHandlerTools eht = new ExceptionHandlerTools();
        // OK Button
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                top = (float)Convert.ToDouble(tbTop.Text);
                bottom = (float)Convert.ToDouble(tbBottom.Text);
                left = (float)Convert.ToDouble(tbLeft.Text);
                right = (float)Convert.ToDouble(tbRight.Text);
            }
            catch
            {
               
                eht.GeneralExceptionHandler("Enter Margins as 3 digit decimals", "(60) Set Margins", false, null);
                return;
            }
            if ((top == 0 || bottom == 0 || left == 0 || right == 0) && headeron)
            {
                Exception ex = new Exception("(Application) - You must set the margins for the header to print correctly\r\n"+
                    "Either set the margins to values greater than 0 or turn the header option off");
                eht.GeneralExceptionHandler("Header will not print if margins are set to 0", "Page Layout", false, ex);
                return;
            }
            if (rbMarginsFormLandscape.Checked)
            {
                landscapemode = true;
            }
            else
            {
                landscapemode = false;
            }
            if (headeron)
            {
                headerstring = tbHeaderText.Text;
                
            }
               
            resultok = true;
            this.Close();


        }
        // SET MARGINS TO 0
        private void button3_Click(object sender, EventArgs e)
        {
            tbTop.Text = "0.0";
            tbBottom.Text = "0.0";
            tbRight.Text = "0.0";
            tbLeft.Text = "0.0";  

        }
        // SET MARGINS TO 1" ALL AROUND
        private void btnOneInch_Click(object sender, EventArgs e)
        {
            tbTop.Text = "1.0";
            tbBottom.Text = "1.0";
            tbRight.Text = "1.0";
            tbLeft.Text = "1.0";
        }
        // HEADER ON BUTTON CHECK CHANGED
        private void rbHeaderOn_CheckedChanged(object sender, EventArgs e)
        {
            if (rbHeaderOn.Checked)
            {
                headeron = true;
            }
            else
            {
                headeron = false;
            }
        }
        // HEADER TEXT BOX HANDLER
        private void tbHeaderText_TextChanged(object sender, EventArgs e)
        {
            headerstring = tbHeaderText.Text;
        }
        // SELECT FONT BUTTON
        private void button4_Click(object sender, EventArgs e)
        {
            DialogResult result;
            try
            {
                fontDialog1.Font = headerfont;
                result = fontDialog1.ShowDialog();
            }
            catch (Exception ex)
            {
                eht.GeneralExceptionHandler("Invalid Font Selection", "(01) Change Font", false, ex);
                return;
            }
            if (result == DialogResult.OK)
            {
                headerfont = fontDialog1.Font;
                tbHeaderFont.Text = headerfont.FontFamily.Name.ToString() + " " + headerfont.SizeInPoints.ToString() + " " + headerfont.Style.ToString();
                

            };
        }

        private void label3_Click(object sender, EventArgs e)
        {

        }
        // PAGE NUMBER BUTTON CHECK CHANGED HANDLER
        private void cbPageNumbersOn_CheckedChanged(object sender, EventArgs e)
        {
            if (cbPageNumbersOn.Checked)
            {
                pagenumberson = true;
            }
            else
            {
                pagenumberson = false;
            }
                       }
        // DATE TIME CHECKBOX HANDLER
        private void cbAddDateToHeader_CheckedChanged(object sender, EventArgs e)
        {
            if (cbAddDateToHeader.Checked)
            {
                adddatetoheader = true;
            }
            else
            {
                adddatetoheader = false;
            }
        }
        // ADD FILENAME CHECKBOX HANDLER
        private void cbAddFileName_CheckedChanged(object sender, EventArgs e)
        {
            if (cbAddFileName.Checked)
            {
                addfilenametoheader = true;
            }
            else
            {
                addfilenametoheader = false;
            }
        }

        // CANCEL BUTTON
        private void button2_Click(object sender, EventArgs e)
        {
            resultok = false;
            this.Close();
        }
        private bool resultok = false;
        // FORM LOAD
        private void marginsForm_Load(object sender, EventArgs e)
        {
            tbTop.Text = top.ToString("F1");
            tbBottom.Text = bottom.ToString("F1");
            tbLeft.Text = left.ToString("F1");
            tbRight.Text = right.ToString("F1");
            if (headerstring != string.Empty)
            {
                tbHeaderText.Text = headerstring;
            }
            
            
            if (headeron)
            {
                rbHeaderOn.Checked = true;
                rbHeaderOff.Checked = false;
            }
            else
            {
                rbHeaderOn.Checked = false;
                rbHeaderOff.Checked = true;
            }
            if (pagenumberson)
            {
                cbPageNumbersOn.Checked = true;
            }
            else
            {
                cbPageNumbersOn.Checked = false;
            }
            if (adddatetoheader)
            {
                cbAddDateToHeader.Checked = true;
            }
            else
            {
                cbAddDateToHeader.Checked = false;
            }
            if (addfilenametoheader)
            {
                cbAddFileName.Checked = true;
            }
            else
            {
                cbAddFileName.Checked = false;
            }
            if (landscapemode)
            {
                rbMarginsFormLandscape.Checked = true;
            }
            else
            {
                rbMarginsFormPortrait.Checked = true;
            }
            tbHeaderFont.Text = headerfont.FontFamily.Name.ToString() + " " + headerfont.SizeInPoints.ToString() + " " + headerfont.Style.ToString();

        }

    }
}

3. Find & Replace - This important editor function is not implemented by the basic RTB. It can be added by creating a separate form to handle entry of the text to search for and/or replace, with the customary function buttons for Find, Find All, Replace, Replace All and the Match Case checkbox. Since runs on top of the main RTB, it uses a delegate to control the search functions: Declare the delegate prototype in Program.cs of your forms application, or in Class.cs if building a dll:

// DELEGATES

    public delegate int Rep(string search, string replace, bool match, int startpos, int function); // used for search call-back

Then add the actual method to perform the task to the form that contains the RichTextBox:

// REPLACE DELEGATE FUNCTION


        public int ReplaceDelegateMethod(string search, string replace, bool match, int startpos, int function)
        {
            const int FIND = 1;
            const int FINDNEXT = 2;
            const int REPLACE = 3;
            const int REPLACEALL = 4;

            /* DEBUGMessageBox.Show("Search = "+search+" Replace = "+replace+" Match = "+match.ToString()
                , "Delegate Test", MessageBoxButtons.OK,
                MessageBoxIcon.Information);*/
            int currentposition = startpos;
            int stopposition = this.rtbMainForm1.Text.Length - 1;  /* text or rtf? */
            switch (function)
            {
                case FIND:
                    {
                        this.rtbMainForm1.Find(search);
                        return (this.rtbMainForm1.SelectionStart);
                    }
                case FINDNEXT:
                    {
                        if (search.Length == 0) // ERROR HANDLER EMPTY SEARCH FIELD
                        {
                            GeneralExceptionForm g = new GeneralExceptionForm("Find Text", "Find Field is Empty", "Error(01) - Replace Dialog", false, null);
                            g.ShowDialog();
                            g.Dispose();
                            return currentposition;

                        }
                        if (startpos < (stopposition)) // changed from stopposition-search.length
                        {
                            int searchresult = 0;
                            /*this.rtbMainForm1.SelectionStart = currentposition;*/
                            if (!match)
                            {
                                searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.None);
                            }
                            else // MATCH CASE
                            {
                                searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.MatchCase);
                            }

                            if (searchresult > 0)
                            {
                                return searchresult;
                            }
                            else
                            {
                                return 0;
                            }

                        }
                        return 0;
                    }
                case REPLACE:
                    {

                        if (replace.Length == 0) // ERROR HANDLER EMPTY REPLACE FIELD
                        {
                            GeneralExceptionForm g = new GeneralExceptionForm("Replace Text", "Replace Field is Empty", "Error(02) - Replace Dialog", false, null);
                            g.ShowDialog();
                            g.Dispose();
                            return currentposition;
                        }
                        
                        if (this.rtbMainForm1.SelectedText.Length > 0) // SKIP IF NONE SELECTED
                        {
                            this.rtbMainForm1.SelectedText = replace;
                        }
                        return currentposition;
                    }
                case REPLACEALL:
                    {
                        if (search.Length == 0 || replace.Length == 0) // ERROR HANDLER EMPTY SEARCH FIELD
                        {
                            GeneralExceptionForm g = new GeneralExceptionForm("Replace All", "Field(s) empty", "Error(03) - Replace Dialog", false, null);
                            g.ShowDialog();
                            g.Dispose();
                            return 0;

                        }
                        int searchresult = 1;
                        int count = 0;

                        while ((currentposition < stopposition) && searchresult >= 0) // changed from stopposition-search.length
                        {


                            if (!match)
                            {
                                searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.None);
                            }
                            else // MATCH CASE
                            {
                                searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.MatchCase);
                            }
                            if (this.rtbMainForm1.SelectedText.Length > 0)
                            {
                                this.rtbMainForm1.SelectedText = replace;
                                count++;
                                currentposition = searchresult + replace.Length;
                            }

                        }
                        dlt.NotifyDialog(this, "Replaced " + count.ToString() + " items.",displaytime);
                        
                        return 1;
                    }

                default:
                    {
                        return 0;
                    }
            }

        }

Finally, call the delegate from the Search & Replace Form:

public partial class ReplaceForm : Form
    {
        public ReplaceForm()
        {
            InitializeComponent();
        }
        // Overload with delegate - prototype def in program.cs
        // Callback Delegate for EditForm to initiate search/replace code
        public ReplaceForm(Rep r)

        {
            InitializeComponent();
            ReplaceDelegate = r; // transer a copy of the delegate to local object
        }
        public ReplaceForm(Rep r,Scr d)
        {
            InitializeComponent();
            ReplaceDelegate = r;
            ScrollDelegate = d;
        }
        public string searchstring
        {
            get
            {
                return SearchString;
            }
            set
            {
                SearchString = value;
            }
        }
        public string replacestring
        {
            get
            {
                return ReplaceString;
            }
            set
            {
                ReplaceString = value;
            }

        }
        public bool matchcase
        {
            get
            {
                return MatchCase;
            }
            set
            {
                MatchCase = value;
            }
        }
        private Rep ReplaceDelegate; // a private copy of the delegate in the constructor
        private Scr ScrollDelegate;  

        
        private void btnReplaceFormCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        private string SearchString = String.Empty;
        private string ReplaceString = String.Empty;
        private bool MatchCase = false;
        private int position = 0; // CHANGED FROM 1, 01-24-2013 missed 1st word
        private const int FINDNEXT = 2;
        private const int REPLACE = 3;
        private const int REPLACEALL = 4;
        private bool foundnext = false;

        private void ReplaceForm_Load(object sender, EventArgs e)
        {
            if (SearchString != String.Empty)
            {
                tbFindWhat.Text = SearchString;
            }
            if (ReplaceString != String.Empty)
            {
                tbReplaceWith.Text = ReplaceString;
            }
            cbMatchCase.Checked = MatchCase;
        }

        private void btnFindNext_Click(object sender, EventArgs e)
        {
            
            int placeholder=0;
            SearchString = this.tbFindWhat.Text;
            placeholder = ReplaceDelegate(SearchString, ReplaceString, MatchCase, position, FINDNEXT);
            ScrollDelegate();
            lblposition.Text = placeholder.ToString() + " " + SearchString;
            if (placeholder != 0)
            {
                position = placeholder+ SearchString.Length;
                foundnext = true;
            }
            else
            {
                position = 0;
                foundnext = false;
                MessageBox.Show("Finished searching through document.", "Search Complete", MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                this.Close();
            }
            
        }

        private void tbFindWhat_TextChanged(object sender, EventArgs e)
        {
            SearchString = tbFindWhat.Text;
        }

        private void tbReplaceWith_TextChanged(object sender, EventArgs e)
        {
            ReplaceString = tbReplaceWith.Text;
        }

        private void cbMatchCase_CheckedChanged(object sender, EventArgs e)
        {
            MatchCase = cbMatchCase.Checked;

        }

        private void btnReplace_Click(object sender, EventArgs e)
        {
            if (!foundnext)
            {
                btnFindNext_Click(sender, e);
                return;  // find next word first
            }
            int placeholder = 0;
            SearchString = this.tbFindWhat.Text;
            placeholder = ReplaceDelegate(SearchString, ReplaceString, MatchCase, position, REPLACE);
            lblposition.Text = placeholder.ToString() + " " + SearchString;
            if (placeholder != 0)
            {
                position = placeholder + SearchString.Length;
                foundnext = false;
            }
            else
            {
                position = 0;
                MessageBox.Show("Finished searching through document.", "Search Complete", MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                this.Close();
            }
        }

        private void btnReplaceAll_Click(object sender, EventArgs e)
        {
            if (ReplaceDelegate(SearchString, ReplaceString, MatchCase, 1, REPLACEALL) == 1)
            {
                this.Close(); // RETURNS 1 if successful, 0 if field(s) are missing
            }
        }
        // SHORTCUTS FOR REPLACE FORM - You can add custom shortcuts here if desired
        private void ReplaceForm_KeyPress(object sender, KeyPressEventArgs e)
        {
            const int CTRLR = 18; // NOTE CTRL: R = 18, L =12, D = 4
            const int CTRLL = 12;
            const int CTRLD = 4;
            const int CTRLA = 1;

            if (System.Windows.Forms.Control.ModifierKeys.ToString() == "Control")
            {
                int result = e.KeyChar;
                switch (result) {

                    case CTRLR:
                        this.tbFindWhat.Text = "";
                        this.tbReplaceWith.Text = "right";
                        break;
                    case CTRLL:
                        this.tbFindWhat.Text = "";
                        this.tbReplaceWith.Text = "left";
                        break;
                    case CTRLD:
                        this.tbFindWhat.Text = "";
                        this.tbReplaceWith.Text = System.DateTime.Today.ToShortDateString();
                        break;
                    case CTRLA:
                        this.tbFindWhat.Text = "*";
                        break;
                    default:
                        break;
                }
                

            }
        }
        //ENTER KEY EVENT HANDLERS
        private void tbFindWhat_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
            {
                e.KeyChar = (char)Keys.Tab;
                e.Handled = true;
                SendKeys.Send(e.KeyChar.ToString());
            }
        }

        private void tbReplaceWith_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
            {
                e.KeyChar = (char)Keys.Tab;
                e.Handled = true;
                SendKeys.Send(e.KeyChar.ToString());
            }
        }

        private void cbMatchCase_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
            {
                e.KeyChar = (char)Keys.Tab;
                e.Handled = true;
                SendKeys.Send(e.KeyChar.ToString());
            }
        }
    }
}

4. Proper Scrolling during Search and Replace: You will notice another delegate in the above code called ScrollDelegate(). This scrolls the selected text found by search and replace to the middle of the RTB window regardless of window size. Otherwise, the selection is always at the bottom of the window. The prototype is:

public delegate void Scr(); // scroll delegate call-back

and the method:

// SCROLL DELEGATE FUNCTION - SCROLL SELECTION UP INTO MIDDLE OF WINDOW
        // Usage: Scrolls selected text to middle line of current window regardless of size
        // Ver: 11-20-2016
        // Credit:  http://stackoverflow.com/questions/205794/how-to-move-scroll-bar-up-by-one-line-in-c-sharp-richtextbox

        public void ScrollDownMethod()
        {
            int topline = rtbMainForm1.GetLineFromCharIndex(rtbMainForm1.GetCharIndexFromPosition(new Point(0, 0)));
            int bottomline = rtbMainForm1.GetLineFromCharIndex(rtbMainForm1.GetCharIndexFromPosition(new Point(rtbMainForm1.ClientSize.Width,
                rtbMainForm1.ClientSize.Height)));
            int currentline = rtbMainForm1.GetLineFromCharIndex(rtbMainForm1.GetFirstCharIndexOfCurrentLine());
            int middleline = topline + ((bottomline - topline) / 2);
            int linestoscroll = currentline - middleline;
            SendMessage(rtbMainForm1.Handle, (uint)0x00B6, (UIntPtr)0, (IntPtr)(linestoscroll));
            return;

        }

You will also need this function from Windows:

[DllImport("user32.dll")]
       static extern int SendMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam);

5. Displaying CapsLock and Insert Key Status in the editor window: Its nice to show the status of these keys in the same manner as other editors. The solution I found, referenced below overrides the base AppIdle event handler and tracks the keystates in realtime, updating two small labels on the form containing the RichTextBox. Note that you must remove the custom handler when the program (main form) closes:

 // Base Constructor
        public EditForm()
        {
            InitializeComponent();
            DoubleBuffered = true;
            Application.Idle += App_Idle;
            
        }
// Custom Application.Idle Event Handler
        // CREDIT: http://stackoverflow.com/questions/577411/how-can-i-find-the-state-of-numlock-capslock-and-scrolllock-in-net
        void App_Idle(object sender, EventArgs e)
        {
            if (System.Windows.Forms.Control.IsKeyLocked(Keys.CapsLock))
            {
                lblCapsOn.Visible = true;
            }
            else
            {
                lblCapsOn.Visible = false;
            }
            if ((GetKeyState(KEY_INSERT) & 1) > 0)
            {
                lblOverStrike.Text = "OVR";
            }
            else
            {
                lblOverStrike.Text = "INS";
            }
        }
        // FOR READING STATE OF INSERT OR CAPS LOCK KEY

        [DllImport("user32.dll")]
        private static extern short GetKeyState(int KeyCode);
        private const int KEY_INSERT = 0X2D;

        // Must Remove Override on Closing
        protected override void OnFormClosed(FormClosedEventArgs e)
        {
            Application.Idle -= App_Idle;
            base.OnFormClosed(e);
        }

6. The Flickering Cursor Problem in Windows 10: In some settings, the way Windows handles the cursor causes it to flicker between an I-Beam and an arrow when editing in an RTB, particularly in Windows 10 with certain display settings. I encountered this when using my editor after upgrading from Windows 7, but I found this solution (originally in Visual Basic), which eliminates the problem, although a side-effect is that the cursor is now fixed as an arrow which means for example that it won't change into a hand as it should when pointing to a web address:

// Prevent Flickering Cursor problem in Windows 10
//CREDIT: http://www.vbforums.com/showthread.php?833547-RESOLVED-Cursor-flicker-RichTextBox-on-Windows-10-Bug
protected override void WndProc(ref Message m)
{
    const int WM_SETCURSOR = 0x20;
    base.WndProc(ref m);
    if (m.Msg == WM_SETCURSOR)
    {
        m.Result = (IntPtr)1;
    }
}

7. Adding Drag and Drop Features: While the RTB supports drag and drop events, the event handlers have to be added to make these work. I used the following generic classes:

// DRAG AND DROP HANDLERS
        // 1st Step in Drag Drop
        // 
        // Attach to Drag Enter Event
        // 
        //
        //
        public void GenericDragEnterEventHandler(object sender, System.Windows.Forms.DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Move;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }
        // 2nd Step in Drag Drop
        // 
        // Returns String from Drag & Drop
        // 
        //
        //
        // 
        public string[] GenericDragDropEventHandler(object sender, System.Windows.Forms.DragEventArgs e)
        {

            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
                return files;

            }
            else
            {
                return null;
            }
        }

Then, you can add a new instance of each to the event handlers for your RTB so the user can drag a file into the window and open that file, or insert and image into the document by dragging it to the RTB. Depending on the file type being dropped, different code is called to handle it:

// DRAG ENTER EVENT HANDLER
        private void rtbMainForm1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
        {
            GenericDragEnterEventHandler(sender, e);
        }
        // DRAG DROP HANDLER
        private void rtbMainForm1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
        {
            string ddfilename = string.Empty;
            string extension = string.Empty;
            ddfilename  = GenericDragDropEventHandler(sender, e)[0];
            if (ftt.FileExists(ddfilename ))
            {
                extension = GetFileExtension(ddfilename);
                
                // HANDLE IMAGE FILE INSERTION
                if (ExtensionIsImageFile(extension))
                {
                    InsertImage(ddfilename);
                    return;
                }
                // INSERT OTHER FILES
                if (rtbMainForm1.TextLength > 0)
                {
                    if (!dlt.QueryDialog(this, "Replace Current File?", "Open A Different File"))
                    {
                        return; // allow dialog to cancel action
                    }
                }
                // RTF OR TEXT
                if (extension == "rtf" || extension == "txt" || extension == "tex" || extension == "doc")
                {
                    LoadText(ddfilename);
                }
                // OPEN OFFICE TEST FILE
                else
                {
                    if (extension == "odt")
                    {
                        ImportODTFile(ddfilename);

                        return; 
                    }
                    // ALL OTHER FILE TYPES
                    else
                    {
                        ImportFile(ddfilename);
                    }
                    
                          
                }
                
            }
        }

8. Miscellaneous Features: Some convenience features I added include Page Up and Page Down buttons which also scroll to the top and bottom of the document when the Control Key is pressed, using this code:

// PAGE UP
        private void btnPgUp_Click(object sender, EventArgs e)
        {
            if (ModifierKeys.HasFlag(Keys.Control))
            {
                rtbMainForm1.SelectionStart = 0;
                rtbMainForm1.SelectionLength = 1;
                rtbMainForm1.ScrollToCaret();
                return;
            }
            else
            {
                rtbMainForm1.Focus();
                SendKeys.Send("{PGUP}");
            }
        }
        // PAGE DOWN
        private void btnPgDn_Click(object sender, EventArgs e)
        {
            if (ModifierKeys.HasFlag(Keys.Control))
            {
                rtbMainForm1.SelectionStart = rtbMainForm1.Text.Length;
                rtbMainForm1.SelectionLength = 1;
                rtbMainForm1.ScrollToCaret();
                return;
            }
            else
            {
                rtbMainForm1.Focus();
                SendKeys.Send("{PGDN}");
            }
        }

When working with some documents, it was useful to remove embedded carriage-return line-feed pairs, which appear as "\r\n" in the actual rich text markup code that underlies the RTB document displayed, so I added this function:

// REMOVE EMBEDDED LINE BREAKS MENU ITEM
        private void removeEmbeddedCRLFsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (rtbMainForm1.SelectedText.Length > 0)
            {
                RemoveCRLFs();
            }
        }
        // REMOVE CRLFS - (Note: dlt.QueryDialog is a generic custom OK Cancel Dialog that returns true if OK is clicked)
        private void RemoveCRLFs()
        {
            if (dlt.QueryDialog(this, "Warning: This will remove all embedded CRLFs permanently. Do You Wish to proceed?", "Remove Embedded Line Breaks")) ;
            string source = rtbMainForm1.SelectedText;
            StringBuilder sb = new StringBuilder();
            foreach (char ch in source)
            {
                if (ch == '\r' || ch == '\n')
                {
                    sb.Append(' ');  // remove hard coded CRLF
                    continue;
                }
                else
                {
                    sb.Append(ch);
                }
            }
            rtbMainForm1.Cut();
            Clipboard.Clear();
            Clipboard.SetData(DataFormats.Text, sb.ToString());
            rtbMainForm1.Paste();
            return;
        }

The result of fixing these problems and omissions is the EditForm.dll class, which can be added to a project and then used by creating an instance of the editor(), customizing it as desired and invoking the method DisplayEditForm(). The .Document property contains the richtext or simple text to edit and is passed back to the caller for use in the application. The main Editor public properties are:

// PUBLIC PROPERTY ACCESSORS
        // Allow Rich Text editing or text only
        public bool AllowRtf
        {
            get
            {
                return _allowrtf;
            }
            set
            {
                _allowrtf = value;
            }

        }
        // Allow file saving and loading from within the editor
        public bool AllowDiscAccess
        {
            get
            {
                return _allowdiscacccess;
            }
            set
            {
                _allowdiscacccess = value;
            }
        }
        // Offer to save the file when closing the editor
        // Disable if using only to edit for a parent application
        public bool UseSaveFileDialogWhenClosing
        {
            get
            {
                return _EnableSaveFileDialogWhenClosing;
            }
            set
            {
                _EnableSaveFileDialogWhenClosing = value;
            }
        }
        // The Document to edit, as RTF or Text
        public string Document
        {
            get
            {
                return _documenttext;
            }
            set
            {
                _documenttext = value;
            }
        }
        // Title of the editor window
        public string WindowTitle
        {
            get
            {
                return _windowtitle;
            }
            set
            {
                _windowtitle = value;
            }
        }
        // Open a Default file if desired
        public string FileToOpen
        {
          
            set
            {
                _filetoopen = value;
            }
          
        }
        // Remember previous editor window size if desired
        public Size StartingWindowSize
        {
            get
            {
                return _startsize;
            }
            set
            {
                _startsize = value;
            }
        }

An additional dll, hcwgenericclasses supports the editor by providing some standardized dialogs for error handling and notifications. Here's an application example from the Demo, which is creates full featured editor called qed.exe, based on EditForm.dll:

using editform;          // INCLUDE IN PROJECT AND REFERENCE
using hcwgenericclasses; // SUPPORTING LIBRARY FUNCTIONS, INCLUDE IN PROJECT AND REFERENCE

// 
    // QED - A Demo Stand Alone Editor using editform.dll
    // HC Williams Copyright (C) 2017 - freeware / opensource GNU public license V3
    // 
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();      
        }
        
        public Form1(string[] arguments) // if run from a command line, load a default file specified
        {
            if (arguments.Length > 0)
            {
                file = arguments[0];
            }
            InitializeComponent();
        }

        // Prevent Low Level WINAPI errors from a recent disk removal
        // Revised: 04-10-2016
        // http://stackoverflow.com/questions/6080605/can-i-use-seterrormode-in-c-sharp-process
        // https://msdn.microsoft.com/en-us/library/aa288468%28v=vs.71%29.aspx#pinvoke_callingdllexport
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621%28v=vs.85%29.aspx

        [DllImport("kernel32.dll")]
        static extern ErrorModes SetErrorMode(ErrorModes uMode);
        [Flags]
        public enum ErrorModes : uint
        {
            SYSTEM_DEFAULT = 0x0,
            SEM_FAILCRITICALERRORS = 0x0001, // the one to use
            SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
            SEM_NOGPFAULTERRORBOX = 0x0002,
            SEM_NOOPENFILEERRORBOX = 0x8000
        }
        private string file = String.Empty;  
        private void Form1_Load(object sender, EventArgs e)
        {
            SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS); // set on startup
            editor ed = new editor();
            ed.AllowDiscAccess = true;
            ed.WindowTitle = "Quick Edit";

            if (file != String.Empty)
            {
                ed.FileToOpen = file;
                
            }
           
            ed.DisplayEditForm(this);
            this.Close();
            
        }
    }
}

Points of Interest

The main points of interest in working on this project were those outlined above. However, there were many other small refinements that I found I had to add, such as properly sizing an image file when dropping it into the document, changing text and background color, and adding bold, underline, and italics buttons,that are documented in the EditForm.dll and demo source code.

History

06-13-2017 1st posting, ver 1056

License

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

Share

About the Author

Howard 9448490
United States United States
I am a retired MD who enjoys programming.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionWell done. Pin
us471120-Jun-17 1:03
memberus471120-Jun-17 1:03 
AnswerRe: Well done. Pin
Howard 944849021-Jun-17 4:39
memberHoward 944849021-Jun-17 4:39 
GeneralThoughts Pin
PIEBALDconsult14-Jun-17 14:21
protectorPIEBALDconsult14-Jun-17 14:21 
GeneralRe: Thoughts Pin
Howard 944849015-Jun-17 2:34
memberHoward 944849015-Jun-17 2:34 
GeneralRe: Thoughts Pin
Howard 94484907-Aug-17 7:42
memberHoward 94484907-Aug-17 7:42 
QuestionSave as Word Doc Pin
MeziLu14-Jun-17 8:47
memberMeziLu14-Jun-17 8:47 
AnswerRe: Save as Word Doc Pin
Howard 944849015-Jun-17 2:31
memberHoward 944849015-Jun-17 2:31 
QuestionDownload links not working.... Pin
Gary Noble13-Jun-17 21:44
memberGary Noble13-Jun-17 21:44 
AnswerRe: Download links not working.... Pin
Howard 944849014-Jun-17 13:08
memberHoward 944849014-Jun-17 13:08 
Generaladding some screenshot will be even better Pin
Southmountain13-Jun-17 14:44
memberSouthmountain13-Jun-17 14:44 
GeneralRe: adding some screenshot will be even better Pin
Howard 944849021-Jun-17 4:40
memberHoward 944849021-Jun-17 4:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171114.1 | Last Updated 18 Jun 2017
Article Copyright 2017 by Howard 9448490
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid