A FULL FEATURED EDITOR BASED ON THE RICHTEXT BOX

    0
    50

    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:

     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: PAGELAYOUT.jpg

     public partial class marginsForm : Form
 {
 public marginsForm()
 {
 InitializeComponent();
 }
 
 public marginsForm(float Top,float Bottom, float Left, float Right,bool landscapemodeon)
 {
 InitializeComponent();
 top = Top;
 bottom = Bottom;
 left = Left;
 right = Right;
 landscapemode = landscapemodeon;
    
 }
 
 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 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 float top, bottom, left, right = 0.0f;
 private bool landscapemode = false;
 private Font headerfont = new Font("Arial", 10); 
 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();
 
 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();
    
    
 }
 
 private void button3_Click(object sender, EventArgs e)
 {
 tbTop.Text = "0.0";
 tbBottom.Text = "0.0";
 tbRight.Text = "0.0";
 tbLeft.Text = "0.0"; 
    
 }
 
 private void btnOneInch_Click(object sender, EventArgs e)
 {
 tbTop.Text = "1.0";
 tbBottom.Text = "1.0";
 tbRight.Text = "1.0";
 tbLeft.Text = "1.0";
 }
 
 private void rbHeaderOn_CheckedChanged(object sender, EventArgs e)
 {
 if (rbHeaderOn.Checked)
 {
 headeron = true;
 }
 else
 {
 headeron = false;
 }
 }
 
 private void tbHeaderText_TextChanged(object sender, EventArgs e)
 {
 headerstring = tbHeaderText.Text;
 }
 
 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)
 {
    
 }
 
 private void cbPageNumbersOn_CheckedChanged(object sender, EventArgs e)
 {
 if (cbPageNumbersOn.Checked)
 {
 pagenumberson = true;
 }
 else
 {
 pagenumberson = false;
 }
 }
 
 private void cbAddDateToHeader_CheckedChanged(object sender, EventArgs e)
 {
 if (cbAddDateToHeader.Checked)
 {
 adddatetoheader = true;
 }
 else
 {
 adddatetoheader = false;
 }
 }
 
 private void cbAddFileName_CheckedChanged(object sender, EventArgs e)
 {
 if (cbAddFileName.Checked)
 {
 addfilenametoheader = true;
 }
 else
 {
 addfilenametoheader = false;
 }
 }
    
 
 private void button2_Click(object sender, EventArgs e)
 {
 resultok = false;
 this.Close();
 }
 private bool resultok = false;
 
 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:

    
    
 public delegate int Rep(string search, string replace, bool match, int startpos, int function); 

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

    
    
    
 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;
    
 
 int currentposition = startpos;
 int stopposition = this.rtbMainForm1.Text.Length - 1; 
 switch (function)
 {
 case FIND:
 {
 this.rtbMainForm1.Find(search);
 return (this.rtbMainForm1.SelectionStart);
 }
 case FINDNEXT:
 {
 if (search.Length == 0) 
 {
 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)) 
 {
 int searchresult = 0;
 
 if (!match)
 {
 searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.None);
 }
 else 
 {
 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) 
 {
 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) 
 {
 this.rtbMainForm1.SelectedText = replace;
 }
 return currentposition;
 }
 case REPLACEALL:
 {
 if (search.Length == 0 || replace.Length == 0) 
 {
 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) 
 {
    
    
 if (!match)
 {
 searchresult = this.rtbMainForm1.Find(search, currentposition, stopposition, RichTextBoxFinds.None);
 }
 else 
 {
 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:FIND_FORM.jpg

    public partial class ReplaceForm : Form
 {
 public ReplaceForm()
 {
 InitializeComponent();
 }
 
 
 public ReplaceForm(Rep r)
    
 {
 InitializeComponent();
 ReplaceDelegate = r; 
 }
 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; 
 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; 
 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; 
 }
 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(); 
 }
 }
 
 private void ReplaceForm_KeyPress(object sender, KeyPressEventArgs e)
 {
 const int CTRLR = 18; 
 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;
 }
 
    
 }
 }
 
 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(); 

    and the method:

    
 
 
 
    
 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:

     
 public EditForm()
 {
 InitializeComponent();
 DoubleBuffered = true;
 Application.Idle += App_Idle;
 
 }
    
 
 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";
 }
 }
 
    
 [DllImport("user32.dll")]
 private static extern short GetKeyState(int KeyCode);
 private const int KEY_INSERT = 0X2D;
    
 
 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:

     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:

    
 
 
 
 
 
 
 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;
 }
 }
 
 
 
 
 
 
 
 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:

    
 private void rtbMainForm1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
 {
 GenericDragEnterEventHandler(sender, e);
 }
 
 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);
 
 
 if (ExtensionIsImageFile(extension))
 {
 InsertImage(ddfilename);
 return;
 }
 
 if (rtbMainForm1.TextLength > 0)
 {
 if (!dlt.QueryDialog(this, "Replace Current File?", "Open A Different File"))
 {
 return; 
 }
 }
 
 if (extension == "rtf" || extension == "txt" || extension == "tex" || extension == "doc")
 {
 LoadText(ddfilename);
 }
 
 else
 {
 if (extension == "odt")
 {
 ImportODTFile(ddfilename);
    
 return; 
 }
 
 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:

    
 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}");
 }
 }
 
 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:

    
 private void removeEmbeddedCRLFsToolStripMenuItem_Click(object sender, EventArgs e)
 {
 if (rtbMainForm1.SelectedText.Length > 0)
 {
 RemoveCRLFs();
 }
 }
 
 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(' '); 
 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 bool AllowRtf
 {
 get
 {
 return _allowrtf;
 }
 set
 {
 _allowrtf = value;
 }
    
 }
 
 public bool AllowDiscAccess
 {
 get
 {
 return _allowdiscacccess;
 }
 set
 {
 _allowdiscacccess = value;
 }
 }
 
 
 public bool UseSaveFileDialogWhenClosing
 {
 get
 {
 return _EnableSaveFileDialogWhenClosing;
 }
 set
 {
 _EnableSaveFileDialogWhenClosing = value;
 }
 }
 
 public string Document
 {
 get
 {
 return _documenttext;
 }
 set
 {
 _documenttext = value;
 }
 }
 
 public string WindowTitle
 {
 get
 {
 return _windowtitle;
 }
 set
 {
 _windowtitle = value;
 }
 }
 
 public string FileToOpen
 {
 
 set
 {
 _filetoopen = value;
 }
 
 }
 
 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:

    QED2.jpg

    using editform; 
    using hcwgenericclasses; 
    
    
 
 
 
 public partial class Form1 : Form
 {
 public Form1()
 {
 InitializeComponent(); 
 }
 
 public Form1(string[] arguments) 
 {
 if (arguments.Length > 0)
 {
 file = arguments[0];
 }
 InitializeComponent();
 }
    
 
 
 
 
 
    
 [DllImport("kernel32.dll")]
 static extern ErrorModes SetErrorMode(ErrorModes uMode);
 [Flags]
 public enum ErrorModes : uint
 {
 SYSTEM_DEFAULT = 0x0,
 SEM_FAILCRITICALERRORS = 0x0001, 
 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); 
 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

    LEAVE A REPLY