locked
How to move a child control inside a parent control with the mouse? RRS feed

  • Question

  • Let's say I have a canvas (or another panel) that contains a control (or even another panel) and I want to allow the user to reposition it by dragging it with the mouse. Now, in my case this is severely complicated by the fact that the control will be a dynamically retrieved component, so any event interception will have to occur in an externally defined common interface, from which they all derive, but let's focus on the simple case, first.

    Let's say I declare a routed event that intercepts the mouse click and would want to use that to change the position of the control relative to its location inside the panel. In Windows Forms (or any GDI-based framework) you have the screen coordinates of the two elements, as well as of the mouse cursor, and you can use those to compute the new location of the control, and you can move it by updating the value of its location. In WPF things work differently, generally, a better approach, but it complicates a task such as this one. Do you fiddle with the Margin property of the control? How do you determine the proper location, especially in view of any other controls present on the same canvas? Any thoughts?

    Kamen


    Just jumped from VS 2005 SP1 to VS 2010, native/managed C++ and C#, transitioning from 32-bit Windows XP to 64-bit Windows 7; Mountain time zone.
    Thursday, June 2, 2011 11:25 PM

Answers

  • Hey Kamen,

     

    Here is a simple example of how to do it in a canvas which uses the Canvas.Left and Canvas.Top properties, if you wanted to do this in another panel type you would have to use margins.

    <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">
      <Canvas MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove" MouseUp="cvsMain_MouseUp" x:Name="cvsMain">
        <Rectangle Height="50" Width="50" Fill="Blue" Canvas.Left="10" Canvas.Top="10"></Rectangle>
        <Ellipse Height="50" Width="50" Fill="Red" Canvas.Left="100" Canvas.Top="100"></Ellipse>
      </Canvas>
    </Window>
    
    

    namespace WpfApplication2
    {
    public partial class MainWindow : Window
    {
    private Shape _currentControl;
    
    public MainWindow()
    {
          
    }
    
    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
    var result = VisualTreeHelper.HitTest(cvsMain, e.GetPosition(cvsMain));
    if (result != null && result.VisualHit is Shape)
    {
    _currentControl = result.VisualHit as Shape;
    }
    }
    
    private void cvsMain_MouseUp(object sender, MouseButtonEventArgs e)
    {
    _currentControl = null;
    }
    
    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
    if (e.LeftButton == MouseButtonState.Pressed && _currentControl != null)
    {
    Point point = e.GetPosition(cvsMain);
    Canvas.SetTop(_currentControl, point.Y - (_currentControl.Height / 2));
    Canvas.SetLeft(_currentControl, point.X - (_currentControl.Height / 2));
    }
    }
    }
    }
    

    • Marked as answer by Kamen Friday, June 3, 2011 2:38 PM
    Friday, June 3, 2011 2:27 AM
  • Kamen,

    Once you find your way to the top parent border (or whatever the parent of your template is) you should be able to get a reference to the custom control itself through the TemplatedParent property on it. 

    Here is an example using the sample i provided above.

    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
          var result = VisualTreeHelper.HitTest(cvsMain, e.GetPosition(cvsMain));
          if (result != null && result.VisualHit is FrameworkElement)
          {
            _currentControl = result.VisualHit as FrameworkElement;
    
            if (_currentControl.TemplatedParent != null)
            {
              _currentControl = _currentControl.TemplatedParent as FrameworkElement;
            }
          }
        }
    
    -Stephen

    • Marked as answer by Kamen Sunday, June 5, 2011 9:01 PM
    Saturday, June 4, 2011 6:13 AM

All replies

  • Hey Kamen,

     

    Here is a simple example of how to do it in a canvas which uses the Canvas.Left and Canvas.Top properties, if you wanted to do this in another panel type you would have to use margins.

    <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">
      <Canvas MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove" MouseUp="cvsMain_MouseUp" x:Name="cvsMain">
        <Rectangle Height="50" Width="50" Fill="Blue" Canvas.Left="10" Canvas.Top="10"></Rectangle>
        <Ellipse Height="50" Width="50" Fill="Red" Canvas.Left="100" Canvas.Top="100"></Ellipse>
      </Canvas>
    </Window>
    
    

    namespace WpfApplication2
    {
    public partial class MainWindow : Window
    {
    private Shape _currentControl;
    
    public MainWindow()
    {
          
    }
    
    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
    var result = VisualTreeHelper.HitTest(cvsMain, e.GetPosition(cvsMain));
    if (result != null && result.VisualHit is Shape)
    {
    _currentControl = result.VisualHit as Shape;
    }
    }
    
    private void cvsMain_MouseUp(object sender, MouseButtonEventArgs e)
    {
    _currentControl = null;
    }
    
    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
    if (e.LeftButton == MouseButtonState.Pressed && _currentControl != null)
    {
    Point point = e.GetPosition(cvsMain);
    Canvas.SetTop(_currentControl, point.Y - (_currentControl.Height / 2));
    Canvas.SetLeft(_currentControl, point.X - (_currentControl.Height / 2));
    }
    }
    }
    }
    

    • Marked as answer by Kamen Friday, June 3, 2011 2:38 PM
    Friday, June 3, 2011 2:27 AM
  • Thank you very much for taking the time to do this! Of course - setting the good-ole attached property on a dependency object! It takes a while to wrap your mind around this new UI technology... I think I'll be able to use this approach. Thanks again.

    Kamen


    Just jumped from VS 2005 SP1 to VS 2010, native/managed C++ and C#, transitioning from 32-bit Windows XP to 64-bit Windows 7; Mountain time zone.
    Friday, June 3, 2011 4:06 PM
  • I successfully made a test project where I am using a user control (with some sample children: a button and an ellipse) as the element to move around (in place of the  fixed rectangle and ellipse from the example above). It all works just fine with the adjustment I made to accommodate the presence of children sub-elements - in the mouse down event, I search for parents until I find my user control type. However, what I really need is a custom control and those are quite a challenge as they are "lookless": if I search for parents, I eventually get to the container in Themes/Generic.xaml (which is a Border, by default; I tried replacing it with a ContentControl but it changed nothing) and not to MyCustomControl class, which is the parent (MyCustomControl itself derives from a common interface and a common base class, the latter deriving from Control).

    The new question is: how do I  deal with the case of children elements when I really want to have a reference (re: "_currentControl" in the code above) to my special base class, since the actual elements are dynamically loaded? Should I post some code to make this clearer (it can get rather involving)?

    Kamen


    Just jumped from VS 2005 SP1 to VS 2010, native/managed C++ and C#, transitioning from 32-bit Windows XP to 64-bit Windows 7; Mountain time zone.
    Saturday, June 4, 2011 2:25 AM
  • Kamen,

    Once you find your way to the top parent border (or whatever the parent of your template is) you should be able to get a reference to the custom control itself through the TemplatedParent property on it. 

    Here is an example using the sample i provided above.

    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
          var result = VisualTreeHelper.HitTest(cvsMain, e.GetPosition(cvsMain));
          if (result != null && result.VisualHit is FrameworkElement)
          {
            _currentControl = result.VisualHit as FrameworkElement;
    
            if (_currentControl.TemplatedParent != null)
            {
              _currentControl = _currentControl.TemplatedParent as FrameworkElement;
            }
          }
        }
    
    -Stephen

    • Marked as answer by Kamen Sunday, June 5, 2011 9:01 PM
    Saturday, June 4, 2011 6:13 AM
  • Stephen, I can't thank you enough for taking the time to help me with this! I've read a book on WPF and I'm in the process of reading a second one; I've read documentation and blogs; I've watched instructional videos, but this WPF is so different from any GUI technology I've dealt with before, it makes my head spin. But I'm sure it's worth it.

    Thanks again!

    Kamen


    Just jumped from VS 2005 SP1 to VS 2010, native C++ and C#, transitioning from 32-bit Windows XP to 64-bit Windows 7; Mountain time zone.
    Sunday, June 5, 2011 9:05 PM
  • No problem at all, glad I could help :)

    -Stephen

    Sunday, June 5, 2011 10:52 PM
  • Nice work Stephen! Thank you for your continuous contributions in forum.


    Min Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, June 7, 2011 6:31 AM