基于富文本框的全功能编辑器

目录

介绍

使用代码

兴趣点


 

介绍

NET Framework中的RichTextBox控件(RTB)提供了一种以所见即所得方式编辑richtext用于RTF文件和较旧的MS-Word文档以及简单文本的标记代码的简单方法。但是,在其基本形式中,它缺乏更完整的编辑器的功能,例如查找和替换、文档打印、页面布局以及文件和图像的拖放。

使用代码

这个项目最初是为了将基RichTextBox类变成一个功能更全的编辑器,它可以作为我正在开发的其他应用程序的可重用组件。尽管RTB有许多有用的属性和方法,但它并不是一个基本形式的全功能编辑器。在添加我认为必要的改进的过程中,以下是我遇到的主要问题,以及来自网络上许多不同来源的解决方案:

1、打印——没有打印RTB内容的内置方法。我创建了一个带有GeneralPrintForm构造函数的PrintHelper类,可在此处作为单独的提示和技巧发布:A Simple RTF Print Form - CodeProject。请注意,网络PrintDialog也提供了一个显示文档将如何打印的PrintPreview窗口。该PrintHelper类使用默认的打印和预览对话框。

2、页面布局和边距——表单上RTB外观的一个相对简单的问题是其内容没有可视边距。解决此问题的简单方法是将RTB包含在稍大的Panel容器中,并带有FixedSingle BorderStyle,因此控件中的滚动文本与面板的周围边缘之间存在明显的边距。请注意,这仅模拟打印页面的边距。要为打印页面设置实际的边距、页眉和页脚,使用上面第1项中的PrintHelper类中的GeneralPrintForm,填充一个System.Drawing.Printing.PageSettings对象并通过其构造函数发送到GeneralPrintForm

// 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);
       }

我创建了一个简单的表单来从编辑器中的用户那里收集这些设置:

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——这个重要的编辑器功能不是由基本的RTB实现的。可以通过创建一个单独的表单来添加它,以处理要搜索和/或替换的文本输入,并使用查找、查找全部、替换、全部替换和匹配大小写复选框的常用功能按钮。由于在主RTB之上运行,因此它使用委托来控制搜索功能:在表单应用程序的Program.cs中声明委托原型,如果构建DLL ,则在Class.cs中声明:

// DELEGATES

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

然后,将执行任务的实际方法添加到包含以下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;
                    }
            }
        }

最后,从搜索和替换表单中调用delegate

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、搜索和替换期间的正确滚动:您会注意到上面代码中的另一个delegate ScrollDelegate()。这会将通过搜索和替换找到的选定文本滚动到RTB窗口的中间,而不管窗口大小。否则,选择始终位于窗口底部。原型是:

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

和方法:

// 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;

        }

您还需要Windows中的此功能:

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

5、在编辑器窗口中显示CapsLock和插入键状态:很高兴以与其他编辑器相同的方式显示这些键的状态。我找到的解决方案(下面引用)覆盖了基本的AppIdle事件处理程序并实时跟踪键状态,更新包含RichTextBox的表单上的两个小标签。 请注意,当程序(主窗体)关闭时,您必须删除自定义处理程序:

// 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、Windows 10中的光标闪烁问题:在某些设置中,Windows处理光标的方式会导致它在RTB中编辑时在I-Beam和箭头之间闪烁,尤其是在具有某些显示设置的Windows 10中。我在从Windows 7升级后使用编辑器时遇到了这个问题,但我找到了这个解决方案(最初在Visual Basic中),它消除了这个问题,虽然副作用是光标现在固定为箭头,这意味着例如当指向一个网址时,它不会变成一只手:

// 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、添加拖放功能:虽然RTB支持拖放事件,但必须添加事件处理程序才能使这些工作。我使用了以下通用类:

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

然后,您可以将每个新实例添加到RTB的事件处理程序中,以便用户可以将文件拖到窗口中并打开该文件,或者通过将文件拖到RTB来将其插入到文档中并生成图像。根据要删除的文件类型,调用不同的代码来处理它:

// 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、其他功能:我添加的一些便利功能包括Page UpPage Down按钮,当按下Control键时,它们也会滚动到文档的顶部和底部,使用以下代码:

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

在处理某些文档时,删除嵌入的回车换行对很有用,它们在显示的RTB文档的实际富文本标记代码中显示为\r\n,因此我添加了此功能:

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

修复这些问题和遗漏的结果是EditForm.dll类,可以将其添加到项目中,然后通过创建 editor()的实例、根据需要定制她病调用DisplayEditForm()方法来使用。该Document属性包含要编辑的richtext或简单文本,并被传递回调用者以在应用程序中使用。主要的编辑器public属性是:

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

附加的DLLhcwgenericclasses通过提供一些用于错误处理和通知的标准化对话框来支持编辑器。这是Demo中的一个应用程序示例,它基于EditForm.dll创建了名为qed.exe的全功能编辑器:

9、

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) 2022 - 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";
            ed.UseAdvancedPrintForm = true; //Added Version 1092
            ed.UseSpeechRecognition = true; //Added Version 1092
            ed.UseSpellCheck = true;        //Added Version 1092
            if (file != String.Empty)
            {
                ed.FileToOpen = file;
            }

            ed.DisplayEditForm(this);
            this.Close();
        }
    }
}

兴趣点

在这个项目上工作的主要兴趣点是上面概述的那些。但是,我发现我必须添加许多其他小的改进,例如在将图像文件放入文档时正确调整其大小、更改文本和背景颜色以及添加粗体、下划线和斜体按钮,这些都记录在EditForm.dll和演示源代码。

版本1071添加了对上标和下标的支持,方法是在所选文本的开头和结尾添加RTF标记\\super\\nosupersubstring,并将修改后的内容重新插入到文档源代码中。还添加了将表格插入文档的功能,使用表单选择所需的行数和列数。这是通过创建包含表格尺寸和属性的富文本标记语言块并使用剪贴板插入来实现的。插入后,您可以在表格单元格中添加和编辑文本。请注意,不能从文档内部编辑表格对象本身,尽管您可以剪切和粘贴它。创建原始表格富文本代码非常复杂。新的源代码向感兴趣的人展示了它是如何工作的。

1075版添加了将扩展的UNICODE字符从当前字体插入到文档中的功能。这包括选定的特殊字符和符号。我在平铺模式下使用了ListView控件。此版本禁用了以十六进制模式导入文件选项,该选项很慢并且效果不佳。

版本1092 2022212

这个版本增加了改进的用户界面外观,以及使用我的zoomprint.dll 项目进行所见即所得打印的选项,而不是更基本的内置打印预览,一个使用Windows内置语音识别的语音听写选项(如果你训练它,效果最好) ,以及使用流行的NHunspell.dll进行基本拼写检查的选项。 在构建项目时,使用这个更复杂的版本需要几个额外的步骤。首先,这些表单是为高分辨率显示而设计的,您的exe文件的兼容性选项卡下的Windows属性可能需要通过将高DPI缩放覆盖启用为由系统执行来进行调整。二、较新版本的NET 4.6.1更高版本将导致NHunspell.dll 出现混合程序集异常,除非您在与应用程序相同的目录中包含具有以下内容的自定义配置文件:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
    </startup>
</configuration>

此文本文件应与您的应用程序同名,即myapplication.exe.config。使用Visual Studio构建项目并从IDE运行时,应启用相同的选项。

如果您想在创建编辑器时使用拼写检查功能,这只是一个要求。如果是这样,您还必须确保NHunspell.dll及其两个字典文件en_US.affen_US.dic也在应用程序目录中。

除了HNunspell.dll之外,其他支持依赖hcwgenericclasses.dlleditform.dllzoomprint.dll可以内置到您的应用程序项目中,因此它们是程序集的一部分,因此不需要作为单独的文件存在使用它。将它们添加到项目中,添加对每个的引用,然后选择构建选项 -> 嵌入式资源然后在Project.cs文件中包含一个Auto Loader处理程序:

static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            // EMBEDDED DLL HANDLER tested OK 01-15-2014
            // Must run in Program Class (where exception occurs
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            Application.Run(new Form1(args));
        }
        // EMBEDDED DLL LOADER
        // VERSION 3.0 10-27-2020 derives resourcename from args and application namespace
        // assumes resource is a DLL
        // this should load any missing DLL that is properly embedded
        // This version corrects null reference exception when AssemblyStream is null
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            string appname = Application.ProductName + "."; // gets Application Namespace
            string[] dll = args.Name.ToString().Split(','); // separates args.Name string
            string resourcename = appname + dll[0] + ".dll"; // element [0] contains the missing resource name
            Assembly MyAssembly = Assembly.GetExecutingAssembly();
            Stream AssemblyStream = MyAssembly.GetManifestResourceStream(resourcename);
            // Revised 10-27-2020
            if (AssemblyStream == null)
            {
                return null;
            }
            // end Rev
            byte[] raw = new byte[AssemblyStream.Length];
            AssemblyStream.Read(raw, 0, raw.Length);
            return Assembly.Load(raw);
        }
    }

当它们没有作为单独的文件找到时,这会从程序集中加载dll。请参阅示例应用程序qed.exe源代码。

https://www.codeproject.com/Articles/1191753/A-Full-Featured-Editor-Based-on-the-Richtext-Box-3

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值