views:

714

answers:

3

I have a TreeView and a Multiline Textbox together on the same form in a Windows form. I have drag and drop setup so that I can drag a node from the TreeView over to the textbox and insert text into the textbox (this is working).

I would like to enhance this so that as the mouse is dragged over the textbox some sort of indicator moves along through the text showing the user where the text will be inserted at, and when dropped it gets inserted at that position. Currently I just put the text at SelectionStart, but the drag operation does not update SelectionStart so its at whereever the user last had the cursor.

Here's 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: 

I think you'll want to look at handling the textBox1_DragOver event. Pass the mouse position contained within the DragOver event args to 'textBox1.GetCharIndexFromPosition()'

You should be able to use the char position to set the caret's position.

Here is the documentation for GetCharIndexFromPosition

Kevin Pullin
+1  A: 

This is actually really irritating as GetCharIndexFromPosition (obviously) is missing one caret position, as there is one extra caret position than there are characters (an extra one at the end). I use this to set SelectionStart on DragOver and Insert on 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 quite perfect, but it does the job. If anyone finds a native version please let me know :)

Jamie Kitson
Very irritating indeed. I may have to try this out. My workaround was to just append an extra space on start drag, so that you could drop at the end of the text and then remove the space on drop.
Ted Elliott
A: 

What I missed in all these advises about the drag-drop is making the text caret visible. Eventually, I have found that you simply need to set focus into the control! So the final code for the textBox1.DragOver event handler will be as follows. I have included 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;
}