DOBON.NET DOBON.NETプログラミング掲示板過去ログ

マウスのキャプチャについて

環境/言語:[Win7 C#2010 .NET3.5]
分類:[.NET]

お世話になります。
独自に作成したコントロールをドラッグドロップで移動、サイズ変更などを実装しようとしています。

コントロールの右端10ポイント以内をドラッグドロップすると横幅を変更するようにしています。ここで、選択した箇所がコントロールの際である場合、左方向へのドラッグではサイズを縮めることができますが、右にドラッグをするとコントロールからフォーカスが外れるせいなのか、サイズを大きくすることができません。
なので、Mouseをキャプチャするようにしてみたのですが、それでも効果は得られませんでした。
どのようにすればいいでしょうか。

以下、細かいソースは省略してますが、マウスダウンイベントでキャプチャして、マウス移動イベントでコントロールの横幅を制御、マウスアップイベントでキャプチャをリリースする流れです。
public partial class EditFocus : PictureBox
{
public void MouseDownFunc(object sender, MouseEventArgs e)
{
this.Capture = true;  //マウスダウンでキャプチャ
}
public void MouseMoveFunc(object sender, MouseEventArgs e)
{
if (m_Cursor == Cursors.SizeWE || m_Cursor == Cursors.SizeNWSE)
{
   //マウスの形状により、横幅を変更
if (this.Width + e.X - m_point.X > 0)
this.Width += e.X - m_point.X;
}
}
public void MouseUpFunc(object sender, MouseEventArgs e)
{
this.Capture = false;
}
}

Captureの使い方が悪いのでしょうか。もしくは、そもそも別手段を使用しないといけないでしょうか。

どちら様かご教示のほど、よろしくお願いします。
■No31833に返信(るるさんの記事)
> 独自に作成したコントロールをドラッグドロップで移動、サイズ変更などを実装しようとしています。

手前味噌ですが、No29978 の ResizablePictureBox コントロールとか。

http://dobon.net/cgi-bin/vbbbs/cbbs.cgi?mode=al2&namber=29949&no=0&KLOG=3
2013/10/04(Fri) 16:48:18 編集(投稿者)

魔界の仮面弁士様
お世話になります。

ご回答、ありがとうございます。教えていただいたソースですんなりと希望通りの動作ができました。また、ソースコードも非常にすっきりしました。

重ねがさね、ありがとうございました。
■No31835に返信(るるさんの記事)
> ご回答、ありがとうございます。教えていただいたソースですんなりと希望通りの動作ができました。また、ソースコードも非常にすっきりしました。

角部の判定が間違ってるところがあるので、適宜修正しておいてください…。
先ほど解決で記載したものの不明点があり、教えていただけないでしょうか。

ご提示のソースコードで実装してみましたが、なにやらPictureBoxの最小サイズが決めれてしまっているようなんです。右端をクリックすると、最小値と思われる横幅に勝手に拡張されてしまいます。
Minimumの影響かと思いましたが、0,0になっており、試しに10,10にしても変わりません。
なぜ勝手にサイズの最小値が決められてしまうのでしょうか。

以上、よろしくお願いします。
■No31837に返信(るるさんの記事)
> ご提示のソースコードで実装してみましたが、

ということは、EditFocus クラスの話では無く、当方の
ResizablePictureBox クラスの話という事でよろしいでしょうか。


> なにやらPictureBoxの最小サイズが決めれてしまっているようなんです。
> 右端をクリックすると、最小値と思われる横幅に勝手に拡張されてしまいます。

再現できなかったので、もう少し情報をください。
ヒットテスト時、WM_NCHITTEST メッセージで何を返していますか?

・フォームに貼ったコントロールのサイズと位置を教えてください。
・クリックした座標を教えてください。
お世話になります。
>ヒットテスト時、WM_NCHITTEST メッセージで何を返していますか

マウスクリックしたタイミングで(IntPtr)HitTest.Rightをかえしているようです。

>フォームに貼ったコントロールのサイズと位置を教えてください。
グループボックス内での座標ですが。X=364,Y=476,W=26,H=26。
右端をクリックするとX=346,Y=476,W=132,H=26に拡張されます。
さらに下端をクリックすると同様に高さが拡張されます。

ひとまず、貼り付けたソースです。
enum HitTest
{
Left = 10,
Right = 11,
Top = 12,
TopLeft = 13,
TopRight = 14,
Bottom = 15,
BottomLeft = 16,
BottomRight = 17,
Border = 18,

Error = -2, // ビープ音ありのNoWhere
Transparent = -1, // 他のウィンドウに覆い隠されている部分
NoWhere = 0, // 背景またはウィンドウ枠
Client = 1, // クライアントエリア
Caption = 2, // タイトルバー
SysMenu = 3, // システムメニュー
SizeBox = 4, // サイズボックス
Menu = 5, // メニュー
HScroll = 6, // 水平スクロールバー
VScroll = 7, // 垂直スクロールバー
MinButton = 8, // 最小化ボタン
MaxButton = 9, // 最大化ボタン
Close = 20, // [閉じる]ボタン
Help = 21, // ヘルプボタン
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (DesignMode) return;

const int WM_NCHITTEST = 0x84;
if (m.Msg != WM_NCHITTEST) return;

Point pos = new Point(m.LParam.ToInt32());
int bw = SystemInformation.FrameBorderSize.Width;
int bh = SystemInformation.FrameBorderSize.Height;
int i=0;

if (m.Result == (IntPtr)HitTest.Client)
{
m.Result = (IntPtr)HitTest.Caption;
}

if (RectangleToScreen(new Rectangle(0, 0, bw, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.TopLeft;
Debug.Print("TopLeft");
}
else if (RectangleToScreen(new Rectangle(Width - bw, 0, bw, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.TopRight;
Debug.Print("TopRight");
}
else if (RectangleToScreen(new Rectangle(0, Height - bh, bw, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.BottomLeft;
Debug.Print("BottomLeft");
}
else if (RectangleToScreen(new Rectangle(Width - bw, Height - bh, bw, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.BottomRight;
Debug.Print("BottomRight");
}
else if (RectangleToScreen(new Rectangle(0, 0, Width, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.Top;
Debug.Print("Top");
}
//else if (RectangleToScreen(new Rectangle(0, bw, Width, bh)).Contains(pos))
// m.Result = (IntPtr)HitTest.Caption;
else if (RectangleToScreen(new Rectangle(0, 0, bw, Height)).Contains(pos))
{
m.Result = (IntPtr)HitTest.Left;
Debug.Print("Left");
}
else if (RectangleToScreen(new Rectangle(Width - bw, 0, bw, Height)).Contains(pos))
{
m.Result = (IntPtr)HitTest.Right;
Debug.Print("Right");
Debug.Print(this.Left.ToString() + "," + this.Top.ToString() + "," + this.Width.ToString()+"," + this.Height.ToString());
}
else if (RectangleToScreen(new Rectangle(0, Height - bh, Width, bh)).Contains(pos))
{
m.Result = (IntPtr)HitTest.Bottom;
Debug.Print("Bottom");
}
else
{
m.Result = (IntPtr)HitTest.Caption;
Debug.Print("Caption");
}
}

なにかわかりますでしょうか。
あと、このコードで移動やサイズ変更ができてしまうのが非常にふしきです。どのような仕組みなんでしょうか。

よろしくお願いします。
■No31839に返信(るるさんの記事)
> >フォームに貼ったコントロールのサイズと位置を教えてください。
> グループボックス内での座標ですが。X=364,Y=476,W=26,H=26。

ということは、こういう配置になるわけですね。

public partial class Form1 : Form
{
    GroupBox groupBox1;
    PictureBox pictureBox1;
    public Form1()
    {
        // InitializeComponent();
        groupBox1 = new GroupBox();
        groupBox1.Dock = DockStyle.Fill;
        Controls.Add(groupBox1);

        pictureBox1 = new ResizablePictureBox();
        pictureBox1.BackColor = Color.Yellow;
        groupBox1.Controls.Add(pictureBox1);

        pictureBox1.SetBounds(364, 476, 26, 26);

        WindowState = FormWindowState.Maximized;
    }
}


> 右端をクリックするとX=346,Y=476,W=132,H=26に拡張されます。
> さらに下端をクリックすると同様に高さが拡張されます。

うーん。やはり再現しません。(他の方はどうでしょうか?)
既存プロジェクトではなく、新規プロジェクトでテストされていますか?

既存プロジェクトだとしたら、修正前のコードの
  this.Width += e.X - m_point.X;
などで阻害されている、といったことはありませんか?



> あと、このコードで移動やサイズ変更ができてしまうのが非常にふしきです。

いわゆる「ヒットテスト」です。

今回使用した WM_NCHITTEST というのは、各ウィンドウに対して、
あらゆるマウスメッセージに先立って送られるメッセージであり、
マウスカーソルがどこの上にあるのかを示す機構です。

たとえば、

・ListBox の IndexFromPoint メソッド
・TreeView の HitTest メソッド
・AccessibleObject の HitTest メソッド

などは、座標を与えると、そこに何があるのかの情報を返す仕組みですよね。
このような機構が、Windows 自体にも備わっていると思ってください。


OS 側は、マウス操作に対して、各ウィンドウに WM_NCHITTEST を
問い合わせて、そこから返却されたヒットテストコードを元に、
リサイズ/移動/何もしない/メニュー表示/最小化などの
既定の動作あるいはイベント処理(ウィンドウメッセージ)を実施します。

PictureBox の場合、全面がクライアント領域であるため、
 base.WndProc(ref m);
で既定の判定処理を行わせると、基本的にはその返却値が
HTCLIENT (先のコードでは HitTest.Client) となります。

今回は、それをタイトルバー(HTCAPTION) だと偽っているわけです。
 if (m.Result == (IntPtr)HitTest.Client)
 {
  m.Result = (IntPtr)HitTest.Caption;
 }

これにより OS 側は、クライアント領域をドラッグしているのではなく、
タイトルバーをドラッグしているものとして扱われ、結果として
pictureBox1.Location が変更されるというわけです。
魔界の仮面弁士様
お世話になります。
原因が分かりました。

コントロールにBorderStyle.FixedSingleをつけてみてください。
サイズが変わっちゃいます。
これ、いったいなぜなんでしょうか。すごい不思議です。
■No31842に返信(るるさんの記事)
> コントロールにBorderStyle.FixedSingleをつけてみてください。
あ、そういうことでしたか。

> これ、いったいなぜなんでしょうか。
このサイズは、
 SystemInformation.MinimumWindowSize
ですね。新規フォームを一つ追加し、それを
 FormBorderStyle = Sizable
にしてみてください。このフォームに設定できる最小サイズです。
OS の種類やスキン(Visual Style)によっても左右されます。


とりあえず、FixedSingle を外して利用してみてください。
枠線が必要なら自前で描画するという事で。
魔界の仮面弁士様
お世話になります。

そのような仕様があったのですね。なんとも不便です。
ひとまず、自前描画を試してみます。
ありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板