Question

I'm trying to make my text labels draggable, where the text floats above all the other elements as you drag it from one panel to another in a c# wpf application.

My labels are made from an array containing the words in a story like so:

foreach (string word in lines)
{
   Label myLabel = new Label();
   myLabel.Content = word;
   myLabel.Name = "lbl" + x;
   myLabel.FontSize = 30;
   myLabel.Margin = new Thickness(0, -10, 0, -10);
   myLabel.FontFamily = new FontFamily("Segoe Print");
   myLabel.MouseDown += new MouseButtonEventHandler(myLabel_MouseDown);
   myLabel.MouseMove += new MouseEventHandler(myLabel_MouseMove);
   myLabel.MouseLeftButtonUp += new MouseButtonEventHandler(myLabel_MouseLeftButtonUp);
   myLabel.MouseUp += new MouseButtonEventHandler(myLabel_MouseUp);
   leftPanel.Children.Add(myLabel);
   myLabelWidth.Add(myLabel.ActualWidth);
   x++;
}

In my events I'm trying to figure out what would move the label outside of it's containing panel into another. In wpf I really can't even understand how to set the label position to the mouse position on a hold mouse down and move.

I have something which doesn't work at all, but looks like this:

void myLabel_MouseMove(object sender, MouseEventArgs s)
{
   Label myLabel = sender as Label
   if(e.LeftButton == MouseButtonState.Pressed)
   {
      myLabel.Left = e.GetPosition(this).X;
      myLabel.Top= e.GetPosition(this).Y;
   }
}

Since this is so far off, I haven't really gotten any further. Any help is appreciated.

Thanks.

Was it helpful?

Solution

There are many ways to implement drag/drop in WPF, and none of them are really trivial. For example you could:

  • Add Thumbs to a Canvas and implement dragstart, dragcomplete and dragdelta
  • Use Adorners. Good article here.
  • Use RenderTransform and create a TranslateTransform to move the control. And manually implement mousedown, mousemove and mouseup on your window/usercontrol.

The following code uses the RenderTransform property of runtime generated Labels to move them around on the screen as the user drags them with the mouse. A limitation of my example is that you cannot have other transforms applied to your labels because it will be overwritten. Consider using a TransformGroup.

Xaml

<Window x:Class="WpfApplication2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Background="Gray" PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown" PreviewMouseMove="Window_PreviewMouseMove" PreviewMouseLeftButtonUp="Window_PreviewMouseLeftButtonUp">
<Grid>
<ItemsControl ItemsSource="{Binding LabelsCollection}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Label HorizontalAlignment="Center" Content="{Binding}"></Label>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

Code behind

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private List<DependencyObject> _hitResultsList = new List<DependencyObject>();
    private Point _currentlyDraggedMouseOffset;
    private Label _currentlyDragged;
    private ObservableCollection<string> _labelsCollection;
    public event PropertyChangedEventHandler PropertyChanged;

    public MainWindow()
    {
        DataContext = this;
        InitializeComponent();
        LabelsCollection = new ObservableCollection<string>();

        for (int i = 1; i <= 10; i++)
        {
            LabelsCollection.Add("Label " + i);
        }
    }

    public ObservableCollection<string> LabelsCollection
    {
        get { return _labelsCollection; }
        set
        {
            _labelsCollection = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("LabelsCollection"));
            }
        }
    }

    private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (_currentlyDragged != null)
        {                
            var mousePos = e.GetPosition(this);
            _currentlyDragged.RenderTransform = new TranslateTransform(mousePos.X - _currentlyDraggedMouseOffset.X, mousePos.Y - _currentlyDraggedMouseOffset.Y);
        }
    }

    private void Window_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        _currentlyDragged = null;
        ReleaseMouseCapture();
    }

    private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        CaptureMouse();
        Point pt = e.GetPosition((UIElement)sender);
        _hitResultsList.Clear();

        VisualTreeHelper.HitTest(this, null,
            new HitTestResultCallback(MyHitTestResult),
            new PointHitTestParameters(pt));

        if (_hitResultsList.Count > 0)
        {
            foreach (DependencyObject d in _hitResultsList)
            {
                var parent = VisualTreeHelper.GetParent(d);
                if (parent != null && parent is Label)
                {
                    _currentlyDragged = parent as Label;
                    if (_currentlyDragged.RenderTransform is TranslateTransform)
                    {
                        _currentlyDraggedMouseOffset.X = e.GetPosition(this).X - ((TranslateTransform)_currentlyDragged.RenderTransform).X;
                        _currentlyDraggedMouseOffset.Y = e.GetPosition(this).Y - ((TranslateTransform)_currentlyDragged.RenderTransform).Y;
                    }
                    else
                    {
                        _currentlyDraggedMouseOffset.X = pt.X;
                        _currentlyDraggedMouseOffset.Y = pt.Y;
                    }

                    return;
                }
            }
        }
        _currentlyDragged = null;
    }

    // Return the result of the hit test to the callback. 
    public HitTestResultBehavior MyHitTestResult(HitTestResult result)
    {
        _hitResultsList.Add(result.VisualHit);
        return HitTestResultBehavior.Continue;
    }

}

OTHER TIPS

In WPF drag and drop, the center of activity is on the DoDragDrop method, and you'll need to use that if you want to have a drag and drop feature in your application.

It's a relatively straight-forward process (as long as you are not orthodox MVVM) to get started...

Identify the control you want to implement. In their vernacular it's called "DragSource". And set your drop zone to AllowDrop=true.

Then you hook the control's MouseDown event and call DoDragDrop. After that, it's a matter of hooking the four callbacks: DragEnter, DragOver, DragLeave, and Drop. Those methods control how the metaphor is visualized on the user surface.

There's a walk-through here if you want to refer to it http://msdn.microsoft.com/en-us/library/za0zx9y0.aspx

If you an orthodox MVVM person, you can use attached behaviours to re-engineer the hooks in the code-behind.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top