AFsoft WebSite(エーエフソフト・ウェブサイト)
 

オペレーティング・システムについて

プログラミングについて
ホームページについて
キャドについて
電子カタログについて
書籍・雑誌
イベント
リンク集
CADを考える:画面操作(2)
前回は単純なボタン操作、キー操作での画面操作について考えましたが、今回はそれ以外の操作での画面操作について考えてみます。
 
これまで画面描画を行った後に画面をリサイズした際、描画されたものが消えてしまいました。また、別画面で覆いかぶせた後も同様に消えてました。その場合、画面を再描画させたいですね。ですので、画面描画を行った場合に True とする変数 DisplayFlag を用意し、初期値は False、画面描画を行ったら True にするようにして、PaintBox1のOnPaintイベントにて再描画を行うようにコーディングしておきます。
 
画面移動といえばスクロール、スクロールといえば、[Additional]内の TscrollBox コンポーネントですが、単純な画像ビューワーであれば、これを配置した上に TImageコンポーネントを配置してやれば、画像サイズによってスクロールバーが表示され、それを操作する事によって画面移動が容易に実現出来ますが、ここで利用しているのは TImage ではなく TPaintBox ですからOnPaintイベント毎に再描画を行う必要があり、CADではかなり拡大表示して高倍率となるため、画像サイズが超巨大化してしまいます。すると頻繁にリソース不足のエラーが発生し、最悪フリーズ、ブルーバック状態と成り得ます。ですのでそれはちょっと出来ません。[Standard]内の TScrollBar を2つ配置します。水平スクロールバーの上に垂直のスクロールバーが乗った状態になると少し不細工ですので、パネルで調整しています。

スクロールバーのMin、Max、Position値はそれぞれ整数型です。ここでは、スクロールバーのMin、Maxの位置が用紙の端になると想定し、これらに -100〜100の値を割付け(0:中央)、位置換算を行って画面表示を行うようにしています。ここでは行っていませんが、拡大倍率が小さい場合には、Min,Maxの値を小さくし、拡大倍率を大きくした場合には、Min,Maxの値を大きくする、という処理を行うのも良いと思われます。但し、整数範囲である、という事と、Min,Maxの値を大きくすればするほど、スクロールバーの[▲][▼]をクリックする回数が多くなる、という事です。その分、細かく移動出来る訳ですが・・・。なお、画面の再描画は、データ量が多くなればなるほど時間が掛かるようになります。 TScrollBar は、タッチ・ジェスチャーのプロパティはありませんが、タッチパネルによるクリック操作は正常に反応します。スクロールバーをタッチパネルで操作する事を前提にする場合は、スクロールバーの大きさを指の大きさ相当になるよう調整する方が良いかもしれません。ディスプレイサイズにも依りますので難しいかもしれませんが。
// 水平スクロールバー
procedure TForm1.ScrollBar1Scroll(Sender: TObject;
 ScrollCode: TScrollCode; var ScrollPos: Integer);
var
 x,l : double;
begin
 if not(DisplayFlag) then exit ;
 // スクロールバー1クリックで移動する量を算出
 x := ScrollBar1.Max - ScrollBar1.Min ;
 if (x < 4.0) then x := 4.0 ;
 x := CData.zp.x / x ;
 // 移動点算出
 x := ScrollPos * x + CData.zp.x/2.0 ;
 
 l := x - (WndX1+WndX2)/2.0 ;
 WndX1 := WndX1 + l ;
 WndX2 := WndX2 + l ;
 
 DisplayAll ;
end;
CADでの画面移動でよく利用される手法としては、作図画面上の1点をクリックし、その点を画面中心点とする操作です。ボタンをクリックして画面中心移動モードになるパターンや、Jw_cad のようにマウス両ボタンを同時クリック・又は・ホイールボタンクリックすると画面中心移動を行うパターン等があります。
それにはまず、画面ドット座標をmm座標に変換する必要があります。mm座標をドット座標に変換する手続きは既に UnitDataGraph.pas で Z2D()という名前で作成していますので、同様に D2Z()という名前の手続きを作成します。
// 用紙mm座標→画面ドット座標
procedure Z2D(var ix,iy:integer;x,y:double);
begin
 ix := Trunc( (x - WndX1)*mm_dot) ;
 iy := Trunc(-(y - WndY2)*mm_dot) ;
end;
 
// 画面ドット座標→用紙mm座標
procedure D2Z(var x,y:double;ix,iy:integer);
begin
 x := WndX1 + ix/mm_dot ; 
 y := WndY2 - iy/mm_dot ;
end;
TPaintBox上でのマウス位置は、
OnMouseDownマウスボタンを押した時
OnMouseMoveマウスを移動した時
OnMouseUpマウスボタンを離した時
の各イベントでそれぞれ取得出来ます。マウスクリックした直後に反応させたい場合は、OnMouseDownイベントで処理します。Jw_cad のように、両ボタンクリックやドラッグ操作がある場合には、両ボタン状態を見る必要があるため、OnMouseUpイベントで処理を行う必要があります。
 
画面移動ボタンをクリックして、作図画面である PaintBox1上をマウスクリックしたら、その位置を中心とする画面移動を行いたいとします。マウスクリックですので、取り合えず、OnMouseDown にて処理を行いますが、DOS時代のように ボタンのOnClickイベント内で
// 画面移動:指定した点を中心に移動する
procedure TForm1.Button23Click(Sender: TObject);
begin
 mx := (マウス位置X取得関数) ;
 my := (マウス位置Y取得関数) ;
 (画面移動処理)
end;
のような事は単純には出来ません。PaintBox1上でマウスクリックするのを待つ必要があるからです。待っている間に他の処理を行うかもしれません。イベントドリブン方式ですから、イベントが発生して初めて、それによる処理が実行できます。ですのでここでは、機能コードを示す変数 FuncCode を用意し、初期値は「0」としてこの場合は何もせず、「1」をセットしている場合には画面移動を行う、というような機能割付けを自分で行って、マウスイベントにてその処理を記述するようにします。
// 画面移動:指定した点を中心に移動する
procedure TForm1.Button23Click(Sender: TObject);
begin
 FuncCode := 1 ;
end;
・・・・
// マウスボタンを押した時
procedure TForm1.PaintBox1MouseDown(Sender: TObject;
 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
 mx,my,lx,ly : double ;
begin
 if not(DisplayFlag) then exit ;
 
 if (ssLeft in Shift) then begin
  // 左クリック時
  D2Z(mx,my, X,Y);
  Case (FuncCode)of
  1: begin
    // C画面移動
    lx := mx - (WndX1+WndX2)/2.0 ;
    ly := my - (WndY1+WndY2)/2.0 ;
    WndX1 := WndX1 + lx ;
    WndX2 := WndX2 + lx ;
    WndY1 := WndY1 + ly ;
    WndY2 := WndY2 + ly ;
    DisplayAll ;
   end;
  end;
 end;
end;
 
また、画面移動でよく耳にする手法といえば、マウスカーソルが「手」の形になって、画面そのものをドラッグ操作し、マウスボタンを離した時に、その画面位置へ画面移動する、という処理です。
どのボタンで行うのか等はありますが、マウスボタンを押した位置から、マウスボタンを離した位置へ画面移動を行うという事、マウスボタンを押した時に画面情報を読み取り、その間のマウス移動時にはマウス位置に、読み取った画面情報を表示させるという事が必要となります。
画面情報を読み取るには、WindowsAPI の BitBlt関数を利用するのが簡単でしょう。読み取った画像データは WrkBmp に入れます。入れたかどうかを WrkBmpFlag で管理します。
// マウスボタンを押した時
procedure TForm1.PaintBox1MouseDown(Sender: TObject;
 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
・・・・
  2: begin
    // D画面移動 始点指示
    if (WrkBmpFlag) then begin
     WrkBmpFlag := False ;
     WrkBmp.Free ;
     WrkBmp := nil ;
    end;
    try
     // 画面情報を取得
     WrkBmp := TBitmap.Create ;
     WrkBmp.Width := PaintBox1.Width ;
     WrkBmp.Height:= PaintBox1.Height ;
     BitBlt(WrkBmp.Canvas.Handle,0,0,WrkBmp.Width,
      WrkBmp.Height,PaintBox1.Canvas.Handle,0,0,SRCCOPY);
     WrkBmpFlag := True ;
    except
     WrkBmpFlag := False ;
     WrkBmp.Free ;
     WrkBmp := nil ;
    end;
    MouseX1 := X ;
    MouseY1 := Y ;
    FuncCode:= 3 ;
   end;
  end;
 end;
end;
そしてマウス移動時には、この画像データをマウス位置に描画します。ここでは単純に WindowsGDI で描画させています。
// マウス移動時
procedure TForm1.PaintBox1MouseMove(Sender: TObject;
 Shift: TShiftState; X,Y: Integer);
・・・
 Case (FuncCode)of
 3: begin
   // D画面移動 始点指示のあとの移動中
   xx := X - MouseX1 ;
   yy := Y - MouseY1 ;
   if (WrkBmpFlag) then begin
    with PaintBox1.Canvas do begin
     Pen.Mode := pmCopy ;
     Pen.Width := 1 ;
     Pen.Color := Cdata.BackColor ;
     Pen.Style := psSolid ;
     Brush.Color := Cdata.BackColor ;
     Brush.Style := bsSolid ;
     Rectangle(0,0,PaintBox1.Width,PaintBox1.Height);
     Brush.Style := bsClear ;
     PaintBox1.Canvas.Draw(xx,yy,WrkBmp);
    end;
   end;
  end;
 end;
・・・
マウスボタンを離した時に、画面移動処理を行います。
// マウスボタンを離した時
procedure TForm1.PaintBox1MouseUp(Sender: TObject;
 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
 mx,my,sx,sy : double ;
begin
 D2Z(mx,my, X,Y);
 Case (FuncCode)of
 3: begin
   // D画面移動 終点指示
   if (WrkBmpFlag) then begin
    WrkBmpFlag := False ;
    WrkBmp.Free ;
    WrkBmp := nil ;
   end;
   D2Z(sx,sy, MouseX1,MouseY1);
   WndX1 := WndX1 + (sx-mx) ;
   WndX2 := WndX2 + (sx-mx) ;
   WndY1 := WndY1 + (sy-my) ;
   WndY2 := WndY2 + (sy-my) ;
   DisplayAll ;
   FuncCode := 2 ;
  end;
 end;
end;
マウスカーソルの形状の変化はここでは行っていませんが、 PaintBox1.Cursor の値を変更すれば可能です。画面全体で反映させたいのなら Screen.Cursor の値を変更します。元の形状に戻す処理も忘れずに入れます。
 
 
さて、次に画面拡大縮小表示について考えます。
 
前回記述した[2倍拡大]及び[1/2倍縮小]は画面中心を基準として行いましたが、指定したマウス位置を基準として [2倍拡大]・[1/2倍縮小]を行いたいという場合があります。
これについては、[C画面移動]と組み合わせたような処理を行えば可能となる、というのは直ぐに想像出来ますので、取り合えず実装してみます。ただ、実装してみると、左クリックで[2倍拡大]、右クリックで[1/2倍縮小]を行いたくなってしまいますが。まぁそれは取り合えず置いておきます。
// マウスボタンを押した時
procedure TForm1.PaintBox1MouseDown(Sender: TObject;
 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
・・・
 4: begin
   // 2倍拡大&画面移動
   lx := mx - (WndX1+WndX2)/2.0 ; // 指定点を中心へ
   ly := my - (WndY1+WndY2)/2.0 ;
   WndX1 := WndX1 + lx ;
   WndX2 := WndX2 + lx ;
   WndY1 := WndY1 + ly ;
   WndY2 := WndY2 + ly ;
   
   mm_dot := 2.0 * mm_dot ;    // 中心を基準に拡大
   lx := (WndX1+WndX2)/2.0 ;
   ly := (WndY1+WndY2)/2.0 ;
   WndX1 := lx - ScrX/mm_dot/2.0 ;
   WndX2 := lx + ScrX/mm_dot/2.0 ;
   WndY1 := ly - ScrY/mm_dot/2.0 ;
   WndY2 := ly + ScrY/mm_dot/2.0 ;
   
   D2Z(lx,ly, X,Y);        // マウス位置へ移動
   lx := lx - (WndX1+WndX2)/2.0 ;
   ly := ly - (WndY1+WndY2)/2.0 ;
   WndX1 := WndX1 - lx ;
   WndX2 := WndX2 - lx ;
   WndY1 := WndY1 - ly ;
   WndY2 := WndY2 - ly ;
   
   DisplayAll ;
  end;
 5: begin
   // 1/2倍縮小&画面移動
   lx := mx - (WndX1+WndX2)/2.0 ; // 指定点を中心へ
   ly := my - (WndY1+WndY2)/2.0 ;
   WndX1 := WndX1 + lx ;
   WndX2 := WndX2 + lx ;
   WndY1 := WndY1 + ly ;
   WndY2 := WndY2 + ly ;
   
   mm_dot := 0.5 * mm_dot ;    // 中心を基準に縮小
   lx := (WndX1+WndX2)/2.0 ;
   ly := (WndY1+WndY2)/2.0 ;
   WndX1 := lx - ScrX/mm_dot/2.0 ;
   WndX2 := lx + ScrX/mm_dot/2.0 ;
   WndY1 := ly - ScrY/mm_dot/2.0 ;
   WndY2 := ly + ScrY/mm_dot/2.0 ;
   
   D2Z(lx,ly, X,Y);        // マウス位置へ移動
   lx := lx - (WndX1+WndX2)/2.0 ;
   ly := ly - (WndY1+WndY2)/2.0 ;
   WndX1 := WndX1 - lx ;
   WndX2 := WndX2 - lx ;
   WndY1 := WndY1 - ly ;
   WndY2 := WndY2 - ly ;
   
   DisplayAll ;
  end;
 end;
end;


 
次に、マウスホイールによる画面拡大・画面縮小について考えます。TPaintBoxには、マウスホイール関連のイベントはありません。TPaintBoxは TPanel の上に置いていますが、TPanel にもマウスホイールのイベントはありません。ではどうするかといえば、TForm にはマウスホイールのイベントがありますのでこれを利用します。2倍拡大・1/2倍縮小は少し大きすぎるので取り合えず、1.2倍拡大・0.8倍縮小としていますが、この値は調整可能にするのも良いと思われます。
// マウスホイールを上へ回転
procedure TForm1.FormMouseWheelUp(Sender: TObject;
 Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
var
 xx,yy : double ;
begin
 if not(DisplayFlag) then exit ;
 
 mm_dot := 1.2 * mm_dot ;
 // 画面中央を基準として拡大縮小します
 xx := (WndX1+WndX2)/2.0 ;
 yy := (WndY1+WndY2)/2.0 ;
 WndX1 := xx - ScrX/mm_dot/2.0 ;
 WndX2 := xx + ScrX/mm_dot/2.0 ;
 WndY1 := yy - ScrY/mm_dot/2.0 ;
 WndY2 := yy + ScrY/mm_dot/2.0 ;
 
 DisplayAll ;
end;
 
// マウスホイールを下へ回転
procedure TForm1.FormMouseWheelDown(Sender: TObject;
 Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
var
 xx,yy : double ;
begin
 if not(DisplayFlag) then exit ;
 
 mm_dot := 0.8 * mm_dot ;
 // 画面中央を基準として拡大縮小します
 xx := (WndX1+WndX2)/2.0 ;
 yy := (WndY1+WndY2)/2.0 ;
 WndX1 := xx - ScrX/mm_dot/2.0 ;
 WndX2 := xx + ScrX/mm_dot/2.0 ;
 WndY1 := yy - ScrY/mm_dot/2.0 ;
 WndY2 := yy + ScrY/mm_dot/2.0 ;
 
 DisplayAll ;
end;
但しこの場合、Form1上に配置している別のコンポーネント上でマウスホイール操作を行う場合でも反応してしまうので、マウス位置が PaintBox1 上にある場合にのみ反応するよう追記します。イベントの引数 MousePos の値はスクリーン座標ですので注意します。
var
p : TPoint ;
x,y,xx,yy : double ;
begin
 p := PaintBox1.ClientToScreen(Point(0,0));
 x := p.X ;
 y := p.Y ;
 p := PaintBox1.ClientToScreen(
     Point(PaintBox1.Width,PaintBox1.Height));
 xx:= p.X ;
 yy:= p.Y ;
 if (MousePos.X >= x)and(MousePos.X <= xx)
 and(MousePos.Y >= y)and(MousePos.Y <= yy) then begin
  ・・・・
なお、タッチパネル操作の拡大縮小に対応させるには、[Gestures]内のTGestureManagerコンポーネントを配置し(GestureManager1となります)、Form1の Touchプロパティ内の GestureManagerプロパティを「GestureManager1」にして、Touchプロパティ内の interactiveGestures内の「igZoom」を「True」にします。すると、それだけで、タッチパネルでの拡大縮小操作は、マウスホイールの上下回転のイベントが発生されますので、それ用のプログラムコードは必要なく実装可能です。

 
次に、マウス2点指示で囲むようにしてその部分を拡大表示する、というような処理がよくあります。
// マウスボタンを押した時
procedure TForm1.PaintBox1MouseDown(Sender: TObject;
 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
・・・
 6: begin
   // 範囲拡大 始点指示
   MouseX1 := X ;
   MouseY1 := Y ;
   FuncCode:= 7 ;
  end;
 7: begin
   // 範囲拡大 終点指示
   D2Z(lx,ly, MouseX1,MouseY1);
   w1 := Abs(mx-lx) ;
   w2 := Abs(my-ly) ;
   if (w1 <= 0.0000001) then w1 := 0.0000001 ;
   if (w2 <= 0.0000001) then w2 := 0.0000001 ;
   w1 := ScrX/w1 ;
   w2 := ScrY/w2 ;
   if (w1 < w2) then
    mm_dot := w1
   else
    mm_dot := w2 ;
   WndX1 := (lx+mx)/2.0 - ScrX/mm_dot/2.0 ;
   WndX2 := (lx+mx)/2.0 + ScrX/mm_dot/2.0 ;
   WndY1 := (ly+my)/2.0 - ScrY/mm_dot/2.0 ;
   WndY2 := (ly+my)/2.0 + ScrY/mm_dot/2.0 ;
   
   DisplayAll ;
   
   FuncCode:= 6 ;
  end;
但しこの場合、始点指示をした後に拡大縮小移動を行うと、始点をマウス位置ドット座標で記憶しているため、おかしくなってしまいます。ですのでそれを考慮する場合は、始点をmm座標で記憶する必要があります。また、どの部分を範囲指定しているのか分かりにくい為、通常は、長方形のラバーバンド表示を行うのが通例です。ラバーバンドは、消し忘れがないよう注意が必要です。ラバーバンドは XORモードでの線描画を行うのが通例ですが、これは線を2度描きして線を消す、というような処理を行います。そのため、線を描画しているのかしていないのかを管理することが重要で、これに失敗をすると画面がラバーバンドのごみだらけ、という状態になります。特に、再描画を行った直後は画面がクリアされるため、ラバーバンドはオフ状態になる、という事を忘れないようにします。また、ラバーバンド描画中に別の処理を行う場合には、ラバーバンドを消す事を忘れないようにします。
 
なお、ラバーバンド描画中、マウスを画面上部に移動すると何故かゴミが大量発生するため、Panel7 を配置し、その上に PaintBox1 を配置するようにしています。



 
今後、マウス操作を行うような処理や、各種コマンドを実行するような場合には、まず最初に、ラバーバンドのチェックを行うべく手続き FuncCheck を実行するように注意します(マウス操作を介在しない画面移動拡大縮小を除く)。
 
Jw_cad でのマウスの両ボタンドラッグ操作による画面移動拡大縮小の機能は実装していませんが、機能的には、上記の機能を利用する事で実装そのものは余り難しくないでしょう(※前倍率表示の場合は、前回の画面状態をバックアップしておく必要があります)。
 
 
 
それでは、ここまでのテストプログラムです。実行ファイル、gdiplus.dll、gdipフォルダは入っていません。ソースのみです。
 
▲前ページ  ▼次ページ
CAD装置(1)
CAD装置(2)
メディア
AutoCADの
DIESELマクロ
CSV
DXF
PCES
IGES
STEP
数学とCAD
CAD作ろ!
CADを考える
 ▲PREV
 ▼NEXT
M7
Jw_cad
 
お問い合わせ 
本サイトはリンクフリーです
リンクバナー
(C)Copyright 1999-2015 By AFsoft All Rights Reserved.