[WPF] 矩形をドラッグしてスイムレーンにスナップさせる

こちらは、仕事用のネタのために公開しておきます。

WPFでコントロールをドラッグ(1) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/709
[win8] パンダ福笑いを作るために ManipulationDelta を使う – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3877

のところで、ManipulationDelta を WinRT(ストアアプリ)で使ったのですが、改めて WPF で作ってみると、ManipulationDelta 関係はタッチイベントでしか発生せず、マウスイベントでは発生しないのですね…勘違いしていました。ストアアプリの場合は、タッチイベントもマウスイベントも統合されているので、問題ないのですが、WPF の場合はやっぱりマウスの移動量を計算する方式ではないと駄目、ということで。

■デザイン時

デザイン時は、こんな風に Grid を使って描画をしておきます。この3つのスイムレーンに、赤、青、黄の3つのタスクを配置する、というイメージです。左下にある薄い赤の矩形は、ドラッグ時の描画用の矩形ですね。

■ドラッグ時のイベント処理

/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        // ドラッグ用の矩形は隠しておく
        manRect.Visibility = System.Windows.Visibility.Collapsed;
        // マウスイベントを割り付ける
        foreach (var el in grid1.Children)
        {
            var rec = el as Rectangle;
            if (rec != null)
            {
                if (rec.Fill == new SolidColorBrush( Colors.Beige)) continue;
                rec.MouseDown += manRect_MouseDown;
                rec.MouseMove += manRect_MouseMove;
                rec.MouseUp += manRect_MouseUp;
            }
        }
    }

    // ドラッグ時のフラグ
    private bool _isDrag = false;
    private Point _dragOffset;
    private Rectangle _capRect;

    /// <summary>
    /// マウスダウンイベント
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseDown(object sender, MouseButtonEventArgs e)
    {
        UIElement el = sender as UIElement;
        if (el != null && e.LeftButton == MouseButtonState.Pressed )
        {
            // ドラッグ開始
            _isDrag = true;
            // オブジェクトとのオフセットを取得
            _dragOffset = e.GetPosition(el);
            // キャプチャ開始
            el.CaptureMouse();
            // ドラッグ対象の矩形(タスク)を保存しておく
            _capRect = (Rectangle)el;
            // ドラッグ用の矩形を表示開始
            manRect.Visibility = System.Windows.Visibility.Visible;
            // ドラッグ用の矩形のサイズを設定
            Point pt = Mouse.GetPosition(board);
            manRect.Width = ((Rectangle)el).ActualWidth;    // 描画時のサイズを取得
            manRect.Height = ((Rectangle)el).ActualHeight;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
            // ドラッグ対象のタスクを半透明に
            _capRect.Opacity = 0.5;

        }
    }

    /// <summary>
    /// マウス移動時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseMove(object sender, MouseEventArgs e)
    {
        if (_isDrag == true)
        {
            Point pt = Mouse.GetPosition(board);
            UIElement el = sender as UIElement;
            manRect.Margin = new Thickness(
                pt.X - _dragOffset.X,
                pt.Y - _dragOffset.Y,
                0, 0);
        }
    }

    /// <summary>
    /// マウスアップ時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void manRect_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (_isDrag == true)
        {
            UIElement el = sender as UIElement;
            // マウスキャプチャをリリース
            el.ReleaseMouseCapture();
            // ドラッグ終了
            _isDrag = false;
            manRect.Visibility = System.Windows.Visibility.Collapsed;
            // タスクをスナッピングさせる
            Point pt = Mouse.GetPosition(grid1);
            // 3x3 の位置で特定
            int col = (int)( pt.X * 3 / grid1.Width);
            int row = (int)(pt.Y * 3 / grid1.Height);
            Grid.SetColumn(_capRect, col);
            Grid.SetRow(_capRect, row);
            _capRect.Opacity = 1.0;
        }
    }
}

元の矩形(タスク)をドラッグしても良いのですが、キャンセルや移動時にもともと何処にあったのかを分かりやすくするために、元の矩形は残したままドラッグさせます。なので、ドラッグ用の矩形(manRect)を移動させています。マウスキャプチャ el.CaptureMouse() をすると、元の矩形の位置からはみ出てもマウス移動を追ってくれるので、これを使います。元の矩形を移動させる場合にもマウスキャプチャを使ったほうが、マウスを高速に移動させたときにも追随します。

矩形のサイズを ActualWidth 等で取ってきているのは、スイムレーンを Grid で作って、これに完全にスナップさせているからです。このため、元の矩形は Width/Height ではサイズが取れないので(設定されていないので)、ActualWidth で実際の描画時のサイズを取得します。

■実行してみる

移動中のカラーを揃えていないのと、横長の青い矩形のスナップ判定がいまいちなのですが、ひとまずドラッグは綺麗に動いています。同じことを、ストアアプリでやる場合には、ManipulationDelta を使うとドラッグ中のフラグとかが必要なくなります。

サンプルはこちら http://sdrv.ms/17ZZ5UF

こんな風なタスクの置き換えツールは、マウスでやるよりもタブレット使ってタッチでやるほうが素早くできるので、Excel で PERT 的にタスク管理をするよりも、何らかのタブレットツールがあると計画の立て直しとかが手早くできるのではないかな…と思いつつ数年たっていますが。まあ、今回のタスク配置の画面で使うといとで、メモ的に。

 

カテゴリー: 開発, C# パーマリンク