这是介绍心爱的UI gesture(dragging和dropping)系列的第一篇帖子

在这第一篇的帖子中,我们会考察最简单的,最直接的方法:增加一系列的窗口事件处理器来更新布局属性.我将会使用VB.NET-转换到C#应该不是很困难的.(译者:因为不懂vb,代码会直接翻译成c#)


第一,我们从window1.xaml的代码开始.
<Window x:Class="SimpleDrapElement.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="SimpleDrapElement" Height="300" Width="300"
    >
<DockPanel>
    <Border DockPanel.Dock="Top" BorderBrush="DarkGray" BorderThickness="2" Padding="4">
      <TextBlock FontSize="8pt" FontFamily="Tahoma" TextWrapping="Wrap">
        <Bold>
          Drag Canvas Sample
        </Bold>
        <LineBreak />
        <Run>
          This sample demonstrates using adorners to drag shapes (or any other element).
        </Run>
      </TextBlock>
    </Border>

    <Canvas Name="MyCanvas">

      <Canvas.Background>
        <LinearGradientBrush>
          <LinearGradientBrush.GradientStops>
            <GradientStop Color="White" Offset="0" />
            <GradientStop Color="DarkBlue" Offset="1" />
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Canvas.Background>
      <TextBox Canvas.Top="120" Canvas.Left="8" />

      <Rectangle Canvas.Top="8" Canvas.Left="8"
       Width="32" Height="32" Fill="LightPink" />
      <Ellipse Canvas.Top="8" Canvas.Left="48"
       Width="40" Height="16" Fill="Tan" />
      <Rectangle Canvas.Top="48" Canvas.Left="8"
       Width="32" Height="62" Fill="Green" />
      <Ellipse Canvas.Top="48" Canvas.Left="48"
       Width="62" Height="62" Fill="Orange" />
    </Canvas>
  </DockPanel>
</Window>

创建了一个简单的窗口,看起来就象这样:

好了,现在让我们挽起袖子,一点一点的往window1.xaml.cs里添加代码.


首先,从window1的constructor正下方开始,我们增加一些代码.

private Point m_StartPoint;// Where did the mouse start off from?
private Double m_OriginalLeft;// What was the element;//s original x offset?
private Double m_OriginalTop;// What was the element;//s original y offset?
private Boolean m_IsDown;// Is the mouse down right now?
private Boolean m_IsDragging;// Are we actually dragging the shape around?
private UIElement m_OriginalElement;// What is it that we;//re dragging?
private Rectangle m_OverlayElement;// What is it that we;//re using to show where the shape will end up?


这就是我们所要探知的如何在Canvas上移动shapes的信息.

现在,让我们开始编写拖拽的代码.当鼠标被点下,我们将获取需要的一些信息.当用户移动鼠标时,我们将会增加一个元件来显示最终的位置.< br/> private void MyCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (e.Source == MyCanvas)
    {
        return;
    }
    m_IsDown = true;
    m_StartPoint = e.GetPosition(MyCanvas);
    m_OriginalElement = e.Source as UIElement;
    MyCanvas.CaptureMouse();
    e.Handled = true;
}
private void MyCanvas_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (m_IsDown)
    {
        if (!m_IsDragging &&
            Math.Abs(e.GetPosition(MyCanvas).X - m_StartPoint.X) > SystemParameters.MinimumHorizontalDragDistance &&
            Math.Abs(e.GetPosition(MyCanvas).Y - m_StartPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
        {
            DragStarted();
        }
        if (m_IsDragging)
        {
            DragMoved();
        }
    }
}

有几件事需要注意的:点击鼠标时我们获取了鼠标(万一用户把鼠标移动到边界外),和DrapStarted(),DragMoved()中使用的 helper方法.另外也必须要注意,考虑到用户的选择,我们使用系统参数的开始拖拽距离设置.这个对笔记本与平板电脑特别有用,因为使用鼠标棒和鼠标板时是很容易引起鼠标晃动的.

现在,要在我们的helper方法中做什么呢?

private void DragStarted()
{
    m_IsDragging = true;
    m_OriginalLeft = Canvas.GetLeft(m_OriginalElement);
    m_OriginalTop = Canvas.GetTop(m_OriginalElement);
    VisualBrush brush = new VisualBrush(m_OriginalElement);
    brush.Opacity = 0.5;
    m_OverlayElement = new Rectangle();

    m_OverlayElement.Width = m_OriginalElement.RenderSize.Width;
    m_OverlayElement.Height = m_OriginalElement.RenderSize.Height;
    m_OverlayElement.Fill = brush;

    MyCanvas.Children.Add(m_OverlayElement);
}
private void DragMoved()
{
    Point currentPosition = Mouse.GetPosition(MyCanvas);
    double elementLeft = currentPosition.X - m_StartPoint.X + m_OriginalLeft;
    double elementTop = currentPosition.Y - m_StartPoint.Y + m_OriginalTop;
    Canvas.SetLeft(m_OverlayElement, elementLeft);
    Canvas.SetTop(m_OverlayElement, elementTop);
}

注意我是如何使用透明的VisulaBrush来显示最终的shape.另外,由于我们使用的是Canvas,所以只要改变Left和top属性.如果是 DockPanel,我将会尝试是否呢个能够snapping边界,或者如果是Grid,我将会试着指出shape要放下的格子.我只是研究了最简单的一个例子,你们自己可以继续深入.
这已经很好了,但我们如何放下shape呢?有两种典型做法,一个是释放鼠标按钮,另一个是按下esc键.让我们来编写它:

private void MyCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (m_IsDown)
    {
        DragFinished(false);
        e.Handled = true;
    }
}
private void Window1_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Escape && m_IsDragging)
    {
        DragFinished(true);
    }
}

另外,一个helper方法很明了的显示了当我们停止拖拽时都发生了什么,这里是它的实现:

private void DragFinished(bool canceled)
{
    Mouse.Capture(null);
    if (m_IsDragging)
    {
        MyCanvas.Children.Remove(m_OverlayElement);
        if (!canceled)
        {
            Canvas.SetLeft(m_OriginalElement, Canvas.GetLeft(m_OverlayElement));
            Canvas.SetTop(m_OriginalElement, Canvas.GetTop(m_OverlayElement));
        }
        m_OverlayElement = null;
    }
    m_IsDragging = false;
    m_IsDown = false;
}

我亲爱的读者们.今天的例子结束了.试着实现,扩展一下他们,惊讶于他们半透明的预览.你能否添加其它的控件来拖拽呢?

(以下非翻译内容)
由于改了c#代码,所以要在Window1的构造函数要修改为:
public Window1()
{
    InitializeComponent();
    MyCanvas.PreviewMouseLeftButtonDown += MyCanvas_PreviewMouseLeftButtonDown;
    MyCanvas.PreviewMouseMove += MyCanvas_PreviewMouseMove;
    MyCanvas.PreviewMouseLeftButtonUp += MyCanvas_PreviewMouseLeftButtonUp;
    this.PreviewKeyDown += Window1_PreviewKeyDown;
}
代码下载:

share your files at box.net