Em WPF, uma Shape (retângulos, elipses, linhas,..) é um FrameworkElement e recebe os eventos de mouse e teclado, como qualquer outro FrameworkElement (botões, por exemplo). Isto é muito diferente de WinForms, onde as formas desenhadas usando GDI+ são apenas desenhos na tela e não recebem nenhum evento. 

Podemos usar isso para movimentar nossas shapes com o mouse usando os eventos PreviewMouseLeftButtonDown, PreviewMouseLeftButtonUp e PreviewMouseMove.

Inicialmente, colocamos algumas shapes no Canvas:

    <Canvas x:Name="canvas1" PreviewMouseLeftButtonDown="canvas1_PreviewMouseDown"
            PreviewMouseMove="canvas1_PreviewMouseMove"
            PreviewMouseLeftButtonUp="canvas1_PreviewMouseUp">
      <Rectangle Canvas.Top="10" Canvas.Left="10" Width="20" Height="40" Fill="Fuchsia" />
      <Ellipse Canvas.Top="25" Canvas.Left="50" Width="40" Height="40" Fill="DarkSeaGreen" />
      <Line Canvas.Top="120" Canvas.Left="50" X1="10" Y1="10" X2="50" Y2="50" Stroke="Navy" StrokeThickness="3"/>
    <Polygon Canvas.Top="50" Canvas.Left="120" Points="30,20 80,24 80,54 30,20" Fill="Red"/>
  </Canvas>
 

Como podemos ver, colocamos os manipuladores de eventos no Canvas. Devido ao recurso de Bubbling e Tunneling, os eventos são propagados por toda a árvore de elementos e, quando clicamos em qualquer uma das shapes, o Canvas recebe o evento também. Assim, podemos processar os eventos em um único local, não nos preocupando de atribuir os manipuladores para cada shape do desenho.

 No código fonte, definimos alguns campos auxiliares:

        Point start; // Ponto base para a movimentação
        int currentZ = 0; // Z-Index atual
        bool isDragging = false; // Está movendo?
        Shape movedElement; // Elemento sendo movido

O manipulador para o evento PreviewMouseLeftButtonDown é:

        private void canvas1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            // Verifica se clicamos numa Shape
            if (e.Source is Shape)
            {
                // Pega posição atual do mouse
                start = e.GetPosition(canvas1);
                // Inicializa variáveis e configura opacidade da shape para 0.5
                isDragging = true;
                movedElement = (Shape)e.Source;
                ((Shape)e.Source).Opacity = 0.5;
                canvas1.CaptureMouse();
                e.Handled = true;
            }
        }

Aqui, inicializamos a variável start com a posição que o mouse foi clicado, configuramos isDragging para true, deixamos o elemento semi-transparente e capturamos o mouse. O manipulador para o evento PreviewMouseMove é:

        private void canvas1_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                Point Pt = e.GetPosition(canvas1);
                // Pega posição atual da shape
                double CurrentLeft = (double)movedElement.GetValue(Canvas.LeftProperty);
                double CurrentTop = (double)movedElement.GetValue(Canvas.TopProperty);

                // Calcula nova posição
                double newLeft = CurrentLeft + Pt.X - start.X;
                double newTop = CurrentTop + Pt.Y - start.Y;

                // Reposiciona elemento
                movedElement.SetValue(Canvas.LeftProperty, newLeft);
                movedElement.SetValue(Canvas.TopProperty, newTop);

                start = Pt;
                e.Handled = true;
            }
        }

Aqui verificamos se estamos arrastando um elemento. Se estivermos, pegamos a nova posição do mouse, recalculamos a posição da Shape e movemos. Como a posição é dada por attached properties, devemos usar SetValue e GetValue para obter e alterar as propriedades Canvas.Left e Canvas.Top. Finalmente, fechamos o movimento no evento PreviewMouseLeftButtonUp:

        private void canvas1_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            // Restaura valores
            movedElement.Opacity = 1;
            movedElement.SetValue(Canvas.ZIndexProperty, ++currentZ);
            isDragging = false;
            canvas1.ReleaseMouseCapture();
        }

Aqui apenas restauramos os valores. Uma coisa que deve ser notada é que estamos alterando a propriedade Canvas.ZIndex. Isto é devido ao fato que queremos que os elementos que movemos fiquem sempre por cima dos outros. Assim, usamos a variável currentZ para guardar o ZIndex, incrementando-o a cada movimento, de maneira que a cada vez que movemos um elemento, ele tenha um ZIndex maior que o anterior.

Desta maneira, podemos mover os elementos dentro de uma janela WPF. Note que este código pode ser estendido a qualquer FrameworkElement, não sendo apenas para Shapes.

2 Thoughts on “Movendo Shapes com o mouse em WPF

  1. Hi … can we have access to the demo source project ??

    thanks in advance

Leave a Reply to El Bruno Cancel reply

Your email address will not be published. Required fields are marked *

Post Navigation