EGSL EGSL - 3 months ago 16
C# Question

Drag and drop an to TextBox in a specific mouse position - Show caret or position indicator

I am pasting an item from a

TreeView
to a
TextBox
, but I want to paste that item in the mouse's current position and also show a caret like the image below.
Image with caret:
example

Here is my code:

private void tvOperador_ItemDrag(object sender, ItemDragEventArgs e)
{
var node = (TreeNode)e.Item;
if (node.Level > 0)
{
DoDragDrop(node.Text, DragDropEffects.Copy);
}
}
private void txtExpresion_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(string))) e.Effect = DragDropEffects.Copy;
}
private void txtExpresion_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(System.String)))
{
string Item = (System.String)e.Data.GetData(typeof(System.String));
string[] split = Item.Split(':');

txtExpresion.Text += split[1];
}
}

TaW TaW
Answer

This is tricky as the Drag&Drop operation keeps the mouse captured, so you can't use the mouse events..

One way is to set up a Timer to do the work..:

    Timer cursTimer = new Timer();

    void cursTimer_Tick(object sender, EventArgs e)
    {
        int cp = txtExpresion.GetCharIndexFromPosition(
                 txtExpresion.PointToClient(Control.MousePosition));
        txtExpresion.SelectionStart = cp;
        txtExpresion.SelectionLength = 0; 
        txtExpresion.Refresh();
    }

The Timer uses the Control.MousePosition function to determined the cursor position every 25ms or so, sets the caret and updates the TextBox.

In your events you initialize it and make sure the TextBox has focus; finally you add the string at the current selection:

    private void txtExpresion_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(typeof(string)))
        {
            e.Effect = DragDropEffects.Copy;
            txtExpresion.Focus();
            cursTimer = new Timer();
            cursTimer.Interval = 25;
            cursTimer.Tick += cursTimer_Tick;
            cursTimer.Start();
        }
    }

    private void txtExpresion_DragDrop(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(typeof(System.String)))
        {
            cursTimer.Stop();

            string Item = (System.String)e.Data.GetData(typeof(System.String));
            string[] split = Item.Split(':');

            txtExpresion.SelectedText = split[1]
        }
    }

A different way to solve it would be to not use normal Drag&Drop and only code the mouse events but this one worked ok a my first tests.

Update

While the above solution does work, using a Timer seems not exactly elegant. Much better to use the DragOver event, as seen in Reza's answer. But instead of painting a cursor, why not do the real thing, i.e. take control of the actual I-beam..?

The DragOver event is called all the time during the move so it works pretty much like MousMove would: So here is a merger of the two solutions, which I believe is the best way to do it:

private void txtExpresion_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(System.String)))
    {
        string Item = (System.String)e.Data.GetData(typeof(System.String));
        string[] split = Item.Split(':');
        txtExpresion.SelectionLength = 0;
        txtExpresion.SelectedText = split[1];
    }
}

private void txtExpresion_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(string)))
    {
        e.Effect = DragDropEffects.Copy;
        txtExpresion.Focus();
    }
}

private void txtExpresion_DragOver(object sender, DragEventArgs e)
{
    int cp = txtExpresion.GetCharIndexFromPosition(
                          txtExpresion.PointToClient(Control.MousePosition));
    txtExpresion.SelectionStart = cp;
    txtExpresion.Refresh();

}
Comments