picoe / Eto

Cross platform GUI framework for desktop and mobile applications in .NET
Other
3.57k stars 325 forks source link

Winforms: Markup pasted into TextArea #1611

Open GeneThomas opened 4 years ago

GeneThomas commented 4 years ago

Expected Behavior

All text in the TextArea should be the same font I am wondering why Eto.Forms on WinForms uses a sub-class of System.Windows.Forms.RichTextBox to implement TextArea? TextArea is not supposed to have markup.

Actual Behavior

Pasted RTF has bold markup show in TextArea screenshot

Steps to Reproduce the Problem

  1. Run the enclosed progam.
  2. It will open wordpage on test.rtf 3 Copy the first line in test.rtf, this contains some bold
  3. Paste that into TextArea

Code that Demonstrates the Problem

EtoFormsFault-TextAreaPaste.zip

Specifications

GeneThomas commented 4 years ago

My fix for this one is to override Ctrl.V and paste the text myself but keeping the scroll position and caret the same is awkward:

// this is a derrived class from Eto.Forms.TextArea

using ewf = Eto.WinForms;
using swf = System.Windows.Forms;

void HandlePaste(object sender, KeyEventArgs e)
        {
            try
            {
                e.Handled = true;
                // always handle otherwise the origional Ctrl.V handler will be called
                string text = Clipboard.Instance.Text;
                if (text == null)
                    return;
                if (Selection.Start <= Selection.End)
                {
                    GtAssert.AreEqual(Selection.Start, CaretIndex);
                    ewfc.TextAreaHandler handler = (ewfc.TextAreaHandler)Handler;
                    swf.TextBoxBase SwfTextbox = handler.Control;
                    ReplaceText(SwfTextbox, Selection.Start, Selection.End - Selection.Start + 1, text, isInsert: true);
                }
                else
                {
                    Insert(text);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError($"HandlePaste exception: {ex.GetCompleteMessage()}");
            }

        }

        public void Insert(string toinsert)
        {
            ewfc.TextAreaHandler handler = (ewfc.TextAreaHandler)Handler;
            swf.TextBoxBase SwfTextbox = handler.Control;
            if (SwfTextbox.SelectionLength == 0)
            {
                ReplaceText(SwfTextbox, CaretIndex, 0, toinsert, isInsert: true);
            }
            else
            {
                ReplaceText(SwfTextbox, SwfTextbox.SelectionStart, SwfTextbox.SelectionLength, toinsert, isInsert: true);
            }
        }

/// <summary>
        /// remove and/or insert without flcikers nor scroll bar jumping
        /// </summary>
        public static void ReplaceText(swf.TextBoxBase swfTextbox, int offset, int length, string insert, bool isInsert)
        {
            Win32.LockWindowUpdate(swfTextbox.Handle);
            // using .Visible does not remove all flicker
            try
            {
                int startIndex = swfTextbox.GetCharIndexFromPosition(new sd.Point(0, 0));
                int caret = swfTextbox.SelectionStart;

                string text = swfTextbox.Text;
                if (length != 0)
                {
                    text = swfTextbox.Text.Remove(offset, length);
                }
                if (insert != "")
                {
                    text = text.Insert(offset, insert);
                }
                swfTextbox.Text = text;

                // puts caret at end
                swfTextbox.SelectionStart = text.Length;
                swfTextbox.ScrollToCaret();

                if (startIndex >= text.Length || text[startIndex] == '\n')
                {
                    swfTextbox.SelectionStart = startIndex;
                }
                else
                {
                    swfTextbox.SelectionStart = startIndex + 1;
                    // winforms gets this wrong and scrolls to the
                    // line above if set to 'startIndex'
                }
                swfTextbox.ScrollToCaret();
                // set scroll bar back

                if (isInsert)
                {
                    if (caret <= offset)
                    {
                        // both insert char and replace selection with char
                        swfTextbox.SelectionStart = caret + insert.Length;
                        return;
                    }

                    // may be impossible
                    if (caret < offset + length)
                    {
                        int dist = (offset + length) - caret;
                        swfTextbox.SelectionStart = caret + insert.Length;
                        return;
                    }
                    swfTextbox.SelectionStart = caret - length + insert.Length;
                    return;
                }
                if (caret <= offset)
                {
                    swfTextbox.SelectionStart = caret;
                    return;
                }
                if (caret > offset + insert.Length)
                {
                    // long word replaced with smaller
                    // would end up after repalcement word
                    swfTextbox.SelectionStart = offset + insert.Length;
                    return;
                }
                if (caret < offset + length)
                {
                    swfTextbox.SelectionStart = caret;// + dist;// + replacement.Length;
                    return;
                }

                // may never happen
                swfTextbox.SelectionStart = caret - length + insert.Length;
            }
            catch (Exception e)
            {
                Trace.TraceError($"Exception in GtTextArea.ReplaceText(): {e.GetCompleteMessage()}");
            }
            finally
            {
                Win32.LockWindowUpdate(IntPtr.Zero);
            }
        }
GeneThomas commented 4 years ago

Ctrl.C copy and Ctrl.X cut also break as they put RTF and text in the clipboard, so pasting into Wordpad breaks. e.g. if you have some-text-in-bold and paste the text copied from a TextArea the pasted text has the font of the TextArea, not adopting the bold as happens when text is copied from notepad.

fix:

        /// <summary>
        ///  the default behavior is to put markup in the text
        ///  so pasting into wordpad breaks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void HandleCopy(object sender, KeyEventArgs e)
        {
            try
            {
                e.Handled = true;
                // always handle otherwise the origional Ctrl.C handler will be called

                if (Selection.End >= Selection.Start)
                {
                    // has selection
                    string text = Text.SafeSubstring(Selection.Start, Selection.End - Selection.Start + 1);
                    ClipboardExtensions.SetClipboardText(text);
                }
                else
                {
                    // no selection
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError($"HandleCopy exception: {ex.GetCompleteMessage()}");
            }

        }
        void HandleCut(object sender, KeyEventArgs e)
        {
            try
            {
                e.Handled = true;
                // always handle otherwise the origional Ctrl.C handler will be called

                if (Selection.End >= Selection.Start)
                {
                    // has selection
                    ewfc.TextAreaHandler handler = (ewfc.TextAreaHandler)Handler;
                    swf.TextBoxBase SwfTextbox = handler.Control;
                    string text = Text.SafeSubstring(Selection.Start, Selection.End - Selection.Start + 1);
                    ClipboardExtensions.SetClipboardText(text);
                    ReplaceText(SwfTextbox, Selection.Start, Selection.End - Selection.Start + 1, "", isInsert: false);
                }
                else
                {
                    // no selection
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError($"HandleCut exception: {ex.GetCompleteMessage()}");
            }

        }