#目的
画面上の図形オブジェクトをマウスドラッグ操作によってユーザが動かせるアプリを作りたい。
#環境
・OS:Windows10
・IDE:Visual Studio 2017
・言語やフレームワーク:C#、WPF
#実現したい仕様
・マウスボタンを押下したら図形を動かし始め、マウスボタンから指を放したら動かすのを止める。
・マウスの動きに沿って図形が平行移動するような感じにする。
・動かす対象の図形を触らなくても操作できるようにする(ウィンドウ枠内のどこを触っても図形が反応するように)。
#実装
1.WPFアプリケーションとしてプロジェクトを作成する。
2.XAMLに動かす対象の図形オブジェクトと、マウス操作を可能にする範囲(具体的に言えばマウスイベントを拾う範囲)を定義する。
またそれぞれの図形はコードビハインドで使うため、x:Nameを指定しておく。
今回はWindow直下にGrid(マウス操作を可能とする範囲)を、その下にRectangleを配置した。
ここで地味にはまったポイントについて少しばかりお話を・・・。
イベントを拾う範囲については、無色だとしてもBackgroundプロパティを設定すること。
「背景色いらんし」という方はBackground="Transparent"とする。
Backgroundを設定しないとサイズを設定していたとしてもイベントが拾われない(Grid, Canvas, DockPanelで確認済み)。
3.定義したオブジェクトに各種イベントプロパティを設定する。
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Sample"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<!-- マウス操作を可能とする範囲(幅と高さを指定せず、Windowいっぱいに広がるようにしている) -->
<Grid
x:Name="OperationArea"
MouseLeftButtonDown="OperationArea_MouseLeftButtonDown"
MouseLeftButtonUp="OperationArea_MouseLeftButtonUp"
MouseMove="OperationArea_MouseMove"
Background="White">
<!-- マウス操作によって動かす対象 -->
<Rectangle x:Name="Target" Width="200" Height="200" Fill="Orange" />
</Grid>
</Window>
4.上記XAMLのコードビハインドに、XAMLで設定したイベントに対応するイベントハンドラを定義する。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace Sample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// マウス押下中フラグ
private bool _isMouseDown;
// マウスの移動が開始されたときの座標
private Point _startPoint;
// マウスの現在位置座標
private Point _currentPoint;
// マウス左ボタン押下イベントのイベントハンドラ
private void OperationArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// フラグを"マウス押下中"にする
_isMouseDown = true;
// GetPositionメソッドで現在のマウス座標を取得し、マウス移動開始点を更新
// (マウス座標は、OperationAreaからの相対的な位置とする)
_startPoint = e.GetPosition(OperationArea);
// イベントを処理済みとする(当イベントがこの先伝搬されるのを止めるため)
e.Handled = true;
}
// マウス左ボタン解放イベントのハンドラ
private void OperationArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// マウス押下中フラグを落とす
_isMouseDown = false;
e.Handled = true;
}
// マウス移動イベントのイベントハンドラ
private void OperationArea_MouseMove(object sender, MouseEventArgs e)
{
// マウス押下中でなければドラッグ操作ではないのでメソッドを抜ける
if (!_isMouseDown)
{
return;
}
// マウスの現在位置座標を取得(OperationAreaからの相対位置)
_currentPoint = e.GetPosition(OperationArea);
//移動開始点と現在位置の差から、MouseMoveイベント1回分の移動量を算出
double offsetX = _currentPoint.X - _startPoint.X;
double offsetY = _currentPoint.Y - _startPoint.Y;
// 動かす対象の図形からMatrixオブジェクトを取得
// このMatrixオブジェクトを用いて図形を描画上移動させる
Matrix matrix = ((MatrixTransform)Target.RenderTransform).Matrix;
// TranslateメソッドにX方向とY方向の移動量を渡し、移動後の状態を計算
matrix.Translate(offsetX, offsetY);
// 移動後の状態を計算したMatrixオブジェクトを描画に反映する
Target.RenderTransform = new MatrixTransform(matrix);
// 移動開始点を現在位置で更新する
// (今回の現在位置が次回のMouseMoveイベントハンドラで使われる移動開始点となる)
_startPoint = _currentPoint;
e.Handled = true;
}
}
}
これでいったんは期待通りに動く。
が、
このままだとウィンドウ(正確に言えばイベントを拾う範囲)からマウスが離れた場合の挙動に問題がある。
ウィンドウから離れたあとにマウスボタンを解放してもMouseULeftButtonUpイベントが拾われないので、マウスポインタをウィンドウ枠内に戻したときにドラッグ操作が継続した状態になってしまう。
これを防ぐために、
イベントを拾う範囲にはMouseLeftButtonUpだけでなくMouseLeaveイベントプロパティも追加し、マウスボタン解放時と同等の処理を行うMouseLeave専用のイベントハンドラを設定する必要がある。
※MouseLeaveイベント:マウスポインタが要素の範囲から離れたときに起きるイベント
<Grid x:Name="OperationArea"
MouseLeave="OperationArea_MouseLeave"
・・・>
// マウスが離れたときのイベントハンドラ
private void OperationArea_MouseLeave(object sender, MouseEventArgs e)
{
_isMouseDown = false;
e.Handled = true;
}