How to move paste to a textbox when making a Drop

I have a TreeView and a multi-line textbox together in one Windows form. I have a drag and drop setup so that I can drag a node from the TreeView to the textbox and paste the text into the textbox (this works).

I would like to improve on this since when dragging the mouse over the text box some kind of indicator moves along the text showing the user where the text will be inserted and when it falls it will be inserted at that position. Currently I am just putting the text at SelectionStart, but the drag and drop operation does not update the SelectionStart, so at the time the user had the last pointer.

Here is my current code:

    private void treeView1_ItemDrag(object sender, ItemDragEventArgs e)
    {
        if (e.Button != MouseButtons.Left)
            return;

        object item = e.Item;
        treeView1.DoDragDrop(((TreeNode)item).Tag.ToString(), DragDropEffects.Copy | DragDropEffects.Scroll);
    }

    private void textBox1_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            e.Effect = DragDropEffects.Copy | DragDropEffects.Scroll;
        }
        else
        {
            e.Effect = DragDropEffects.None;
        }
    }

    private void textBox1_DragDrop(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            textBox1.SelectionLength = 0;
            textBox1.SelectedText = (string)e.Data.GetData(DataFormats.StringFormat);
        }
    }

      

+1


a source to share


5 answers


I think you need to look at handling the textBox1_DragOver event. Pass the mouse position contained in the DragOver event argument to 'textBox1.GetCharIndexFromPosition ()'

You can use char position to set carriage position.



Here is the documentation for GetCharIndexFromPosition

+3


a source


What I missed in all of these drag-and-drop tips makes the text visible. In the end, I found that you just need to set focus to control! So the final code for the textBox1.DragOver event handler would be as follows. I've included the GetCaretIndexFromPoint function from the previous answer:



/// <summary>
/// Gives visual feedback where the dragged text will be dropped.
/// </summary>
private void textBox1_DragOver(Object sender, System.Windows.Forms.DragEventArgs e)
{
    // fake moving the text caret
    textBox1.SelectionStart = GetCaretIndexFromPoint(textBox1, e.X, e.Y);
    textBox1.SelectionLength = 0;
    // don't forget to set focus to the text box to make the caret visible!
    textBox1.Focus();
}

/// <remarks>
/// GetCharIndexFromPosition is missing one caret position, as there is one extra caret
/// position than there are characters (an extra one at the end).
/// </remarks>
private int GetCaretIndexFromPoint(System.Windows.Forms.TextBox box, int x, int y)
{
    Point realPoint = box.PointToClient(newPoint(x, y));
    int index = box.GetCharIndexFromPosition(realPoint);
    if (index == box.Text.Length - 1)
    {
        Point caretPoint = box.GetPositionFromCharIndex(index);
        if (realPoint.X > caretPoint.X)
        {
            index += 1;
        }
    }
    return index;
}

      

+8


a source


This is actually really annoying as GetCharIndexFromPosition is (obviously) missing one caret position, as there is one extra caret position than there are characters (extra at the end). I'm using this to set SelectionStart to DragOver and Insert to DragDrop.

    private int GetCaretIndexFromPoint(TextBox tb, int x, int y)
    {
        Point p = tb.PointToClient(new Point(x, y));
        int i = tb.GetCharIndexFromPosition(p);
        if (i == tb.Text.Length - 1)
        {
            Point c = tb.GetPositionFromCharIndex(i);
            if (p.X > c.X)
                i++;
        }
        return i;
    }

      

Not exactly perfect, but it gets the job done. If anyone finds a native version please let me know :)

+2


a source


I was looking for an implementation of this WPF solution and couldn't find one. So it is implemented as follows (Credits to the original author of the solution). Hope this helps someone looking for a WPF version.

    /// <summary>
    /// Handles the Preview DragOver event to set the textbox selection at the precise place where the user dropped the dragged text
    /// </summary>
    private static void element_PreviewDragOver(object sender, DragEventArgs dragEventArgs)
    {
        TextBox textBox = sender as TextBox;
        if (textBox != null && dragEventArgs != null)
        {
            // Set the caret at the position where user ended the drag-drop operation
            Point dropPosition = dragEventArgs.GetPosition(textBox);

            textBox.SelectionStart = GetCaretIndexFromPoint(textBox, dropPosition);
            textBox.SelectionLength = 0;

            // don't forget to set focus to the text box to make the caret visible!
            textBox.Focus();
            dragEventArgs.Handled = true;
        }
    }

    /// <summary>
    /// Gets the caret index of a given point in the given textbox
    /// </summary>
    /// <param name="textBox"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    private static int GetCaretIndexFromPoint(TextBox textBox, Point point)
    {
        int index = textBox.GetCharacterIndexFromPoint(point, true);

        // GetCharacterIndexFromPoint is missing one caret position, as there is one extra caret position than there are characters (an extra one at the end).
        //  We have to add that caret index if the given point is at the end of the textbox
        if (index == textBox.Text.Length - 1)
        {
            // Get the position of the character index using the bounding rectangle
            Rect caretRect = textBox.GetRectFromCharacterIndex(index);
            Point caretPoint = new Point(caretRect.X, caretRect.Y);

            if (point.X > caretPoint.X)
            {
                index += 1;
            }
        }
        return index;
    }

    /// <summary>
    /// Handler for preview drag event in a textbox
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="dragEventArgs"></param>
    private static void element_PreviewDrop(object sender, DragEventArgs dragEventArgs)
    {
        TextBox textBox = sender as TextBox;
        if (textBox != null && dragEventArgs != null && dragEventArgs.Data != null && dragEventArgs.Data.GetDataPresent(DataFormats.StringFormat))
        {
            string newText = dragEventArgs.Data.GetData(DataFormats.StringFormat) as string;
            if (!string.IsNullOrEmpty(newText))
            {
                // do your custom logic here
                textBox.Focus();
            }
            dragEventArgs.Handled = true;
        }
    }

      

+1


a source


To emulate the behavior of common tools (such as Notepad), the GetCaretIndexFromPoint () function must check the X and Y coordinates.

/// <summary>
/// Gives visual feedback where the dragged text will be dropped.
/// </summary>
private void textBox1_DragOver(Object sender, System.Windows.Forms.DragEventArgs e)
{
    // fake moving the text caret
    textBox1.SelectionStart = GetCaretIndexFromPoint(textBox1, e.X, e.Y);
    textBox1.SelectionLength = 0;
    // don't forget to set focus to the text box to make the caret visible!
    textBox1.Focus();
}

/// <remarks>
/// GetCharIndexFromPosition is missing one caret position, as there is one extra caret
/// position than there are characters (an extra one at the end).
/// </remarks>
private int GetCaretIndexFromPoint(System.Windows.Forms.TextBox box, int x, int y)
{
    Point realPoint = box.PointToClient(new Point(x, y));
    int index = box.GetCharIndexFromPosition(realPoint);
    if (index == box.Text.Length - 1)
    {
        Point caretPoint = box.GetPositionFromCharIndex(index);
        if ((realPoint.X > caretPoint.X) || (realPoint.Y > caretPoint.y))
        {
            index += 1;
        }
    }
    return index;
}

      

You can see the difference if the last character in the textbox is a newline after the line of text and you leave the text below and to the left of the carriage in line with below and to the right of the carriage.

Thanks user468106 for the great answer!

+1


a source







All Articles