VCのようなオブジェクトの貼り付け処理について

[戻る]
裕里 2007/02/13(火) 12:25:23
開発環境はwindowsXp VC++2005Sp1 MFC SDIで行っております。

作成したいことは、下記の内容になりますが、実際には難しく困っております。

最終目標:VC++でダイアログ上にコントローラを貼り付けるよう動作を
          プログラムにて作成したい。

その過程で、ダイアログバーより、作成するコントローラをダイアログに貼り付ける
のですが、デザインモードと実行モードを切り分けたく。
デザインモード中は、コントローラの移動、リサイズを可能にし、
実行中は、コントローラの実行のみを行いたく思っております。

そこで現在デザインモードについて思考中なのですが、
デザインモード中は、各コントローラを表すビットマップをダイアログ上に
貼り付けて、移動、リサイズを可能にしたいと思っております。

まずは、このような動作を行う場合で、何かしらのコントローラにビットマップを
割付たいのですが、どのようにしたら実現できるかわかりません。

説明が拙くてわかりにくいと思いますが、どうかご教授いただけないでしょうか?

たいちう 2007/02/13(火) 13:12:53
とりあえず、コントロール(ボタン)にビットマップ貼り付け。
http://www.athomejp.com/goldfish/mfc/button/setbitmap.asp

# 最終目標の道のりはかなり長そうですよ。
# 本当にやる必要があるか、何度か立ち止まって考えると良いと思います。

PATIO 2007/02/13(火) 17:31:34
コントロールの間違いですよね、多分。

コントロールのイメージをビットマップでどうにかすると言う話ですが、
移動はともかく、サイズの変更はどうするのだろうと思ったり。
単純にコンボボックスのビットマップを拡大してしまうとリストボックスを
表示させる為のボタンが変形してしまうとかそういう不都合が起こりそうです。
思うに結構面倒そうです。
かえってビットマップにするよりも自前で簡易描画した方が楽そうです。
たいちうさんの言われる通り、本当に必要がある処理なのか考え直した方が
良いかもしれません。苦労の割には結果が伴わないかも。

裕里 2007/02/14(水) 15:04:27
ご回答ありがとうございます。
何も進まないままになってます(>_<)。

まず、VCのコントロールのようにダイアログに貼り付け、
コントロールを選択した場合に

@コントロールの周りに変更枠を出す。
Aカーソルが変更する


上記を試みているのですが、@は絶望的に案がなくAも試しているのですが
うまくいきません。


//-----------------------------
// カーソルの変更
//-----------------------------
HCURSOR    cusor    =(HCURSOR)LoadImage(NULL,                
                  MAKEINTRESOURCE(IDC_*****),
                  IMAGE_CURSOR,
                  0,
                  0,
                  LR_DEFAULTSIZE | LR_SHARED);
this->SetCursor(cusor);

keichan [E-Mail] 2007/02/14(水) 15:31:05
@・・・ダイアログに直書きすると意外に楽かもしれません
A・・・コードは間違えてはいないかと思います。
   エラーチェックしてみてください。

裕里 2007/02/14(水) 16:05:50
現在上記処理をダイアログに貼り付けたコントロールの中でやっておりますが、
カーソルの変更には、親のダイアログにて行う必要がありますか?
親のダイアログに渡して行ったら、変更されました。
ありがとうございます。

裕里 2007/02/14(水) 18:46:43
@コントロールの周りに変更枠を出す
上記って、可能なのでしょうか、具体的な案が思い出せず、苦しんでます。(>_<)
どうかご教授いただけないでしょうか?

通りすがり 2007/02/15(木) 02:58:16
WM_SETCURSOR ならボタンなどのコントロール上でも送られてくるみたいなので、そこで枠の表
示や、カーソルの変更などをすると良いと思います。

BOOL CXXXDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
 CRect rect;
 CClientDC dc(this);
 CBrush frame_brush, erase_brush; // 毎回使うのでメンバにしても良いと思います
 CPoint point;

  if(!m_bEdit) return TRUE; // 編集モードじゃなければ何もしない

 switch(message){
  case WM_LBUTTONDOWN :
   break;
  case WM_RBUTTONDOWN :
     if(pWnd != this){
        m_bDrag = TRUE;
     }
   break;
    case WM_RBUTTONUP :
      if(m_bDrag)
         m_bDrag = FALSE;
      break;
  case WM_MOUSEMOVE :
   if(m_pFucos != this && m_bDrag){
         サイズ、位置の変更
      }

   //  別のコントロールの上にカーソルが移動したら、上から塗りつぶして枠を消す
   if(pWnd != m_pFocus && m_pFocus != this)
   {
    // ダイアログ領域の色のブラシを作成
    erase_brush.CreateSysColorBrush(COLOR_BTNFACE);
    // ボタンなどのコントロールに外接する四角形の取得 
    m_pFocus->GetWindowRect(&rect);
       // GetWindowRectはScreen上での座標を返すのでダイアログ上の座標に変更
    this->ScreenToClient(&rect);

    rect.InflateRect(3, 3, 3, 3);
    
    // 以前の選択枠の上に書いて、選択枠を消す
    dc.FrameRect(&rect, &erase_brush);
    SetCursor(m_hArrow);
        m_Mode = NON;
   }

   // ボタンなどのコントロール上なら選択枠を描く
   if(pWnd != this){
    // 枠様のブラシを作成
    frame_brush.CreateStockObject(BLACK_BRUSH);

    pWnd->GetWindowRect(&rect);
    ScreenToClient(&rect);

    // 選択枠を表示するために適当に枠を広げる
    rect.InflateRect(3, 3, 3, 3);
    // 選択枠を表示
    dc.FrameRect(&rect, &frame_brush);
            
       // マウスカーソルの位置を取得(この関数もスクリーン座標で返される)
    GetCursorPos(&point);
    this->ScreenToClient(&point);

    // 適当に枠を縮めて枠の外に出たら、サイズ変更カーソルを表示
    rect.InflateRect(-6, -6, -6, -6);
    if(point.x < rect.left || point.x > rect.right){
     SetCursor(m_hWSize);  // 幅変更カーソル
    }
    else if(point.y < rect.top || point.y > rect.bottom){
     SetCursor(m_hHSize);  // 高さ変更カーソル
    }
    else{
    SetCursor(m_hCursor); // コントロール上用のカーソル
    }
   }
   else{
        SetCursor(m_hArrow); // コントロール上に無ければ標準カーソルを表示
     }

   m_pFocus = pWnd;  // カーソルの下にあるコントロールのポインタをメンバにセット
 }
 return TRUE; // デフォルト処理でカーソルの変更をされないようにTRUEを返す
 //return CDialog::OnSetCursor(pWnd, nHitTest, message);
}

初期化で、カーソルのハンドルの設定、と標準カーソルのセット(カーソル処理を無効化してい
るため)m_pFocus に this を設定しおいてください。

右ドラッグ中に、SetWindowPos でサイズや、位置の変更をしてやると良いと思います。

左クリックをするとボタンがへこんでしまうので、右クリックを使ったほうが簡単だと思いま
す。オーナードローでへこまないようにも出来ますが、ボタンなどの表示を全部自分で書かない
といけないので、大変です。

やや強引なやり方かもしれないので、参考までにしてください。
それから、VC++6.0 で試しただけなので、2000 では仕様が変わっているかもしれません。

通りすがり 2007/02/15(木) 03:32:45
訂正
    case WM_RBUTTONDOWN :
     if(m_pFocus != this){
        m_bDrag = TRUE;
     }
   break;


    //  別のコントロールの上にカーソルが移動したら、上から塗りつぶして枠を消す
  if(pWnd != m_pFocus && m_pFocus != this)
  {
      Invalidate();
  }
    再描画をしたほうが簡単、かつ綺麗に枠が消えます。

裕里 2007/02/15(木) 09:56:59
ありがとうございます。
早速試してみます。

PATIO 2007/02/15(木) 12:29:00
思うんですが、何処まで編集中の表示に凝るか次第だと思います。
編集モードでは、コントロールの種類が判別できる程度の簡易描画を
行うようにしておいて各コントロールの種類の応じてサイズから
そのコントロールの簡易表示を描画するような関数を用意しておいて
編集中はその簡易表示で対応する。
で、VCのリソースエディタのテスト表示のような感じで実際のイメージ表示と
分けて考えてしまった方がシンプルになりそうな気がします。
実際のコントロールを使ってしまうとどうしても通常のクリック時の動作まで
再現してしまいますから派生クラス使って既存の処理を潰しておいて
編集時にはそっちのクラスでサブクラス化するとかしないとうまくいかないのでは無いか
なぁ。

PATIO 2007/02/15(木) 12:38:00
枠の話ですけれど、各コントロールはウインドウサイズとコントロールのサイズが
同じですからコントロール内でウインドウサイズの外には描画できないのではないかなと。
ウインドウサイズの内側にコントロールに多少かかる形であれば、描画も可能ではないか
と思います。
コントロールに掛かるのが気に入らないなら親ウインドウのクライアント領域にコント
ロールを
囲むように描画しないと表示されないのではないでしょうか?
あと、コントロールに対してGDIで描画処理をしても領域が無効化されていないと
描画処理が画面に反映されないと思います。
これは、Windowsでは割と基本的な話なので抑えておいた方がよろしいかと。
画面を更新したい場合は、更新したい領域が無効化されている必要があります。

裕里 2007/02/15(木) 16:43:20
枠については、なんとかそれらしくコントロールの隅に■の選択枠を出すようにして、
そこの座標にマウスが乗った場合にマウスを変更するようにしました。

とりあえず、前準備としては、それらしくなりました。

次は、下記に取り組みと考えております
@選択された状態でリサイズされた場合
A選択された状態で移動された場合

@Aとも、マウスを押下している状態を管理して、コントロールを移動/リサイズ
させるのではなくて、VCのように点線のラインで引っ張りだして、確定した状況。
マウスを離した状況で、実際のコントロールの移動をしようと考えております。

■の線は、dc.FrameRect(&rect, &erase_brush);
を参照して8つ書いたのですが、点線とかもできるのでしょうか?
長々となりましたが、皆様のおかけでちょっとづつ進んで参りました。
また、お世話になりますが、ご教授ください。
宜しくお願い致します。(^_^)

たいちう 2007/02/15(木) 19:28:08
例えばこんなのかな?

CRect rect(50, 50, 100, 100);
CPen pen(PS_DOT, 1, RGB(0, 0, 0));
dc.SelectObject(&pen);
dc.SetBkColor(GetSysColor(COLOR_BTNFACE));
dc.SelectStockObject(NULL_BRUSH);
dc.Rectangle(rect);
dc.SelectStockObject(BLACK_PEN);

裕里 2007/02/16(金) 14:46:38
たいちろうさん、ありがとうございます。
なんとかそれっぽく、見えるように近づきました。

ただ、なにぶん初心者で、点線の残像の消し方がわかりません。
塗りつぶしたり、いろいろ試したのです。

RedrawWindowやUpdateWindowとかを使用するのでしょうか。
度々申しあけありません。また、ご教授ください。(>_<)

たいちう 2007/02/16(金) 15:22:02
> ただ、なにぶん初心者で、点線の残像の消し方がわかりません。
> 塗りつぶしたり、いろいろ試したのです。

Invalidateでwindowの再描画ができます。
Invalidateは、windowの領域が再描画の必要があることを
OSに教えてあげる関数ですので、そのうちOnPaint等が呼ばれて再描画されます。
『そのうち』で問題なら、UpdateWindowです。
InvalidateRectも似ていますが、window全体でなく指定した領域のみが
再描画の対象になります。領域が限られている場合は、再描画の処理が
軽いですが、領域をプログラムが指定してやる必要があります。

※ 表示がちらつく場合は、『ダブルバッファリング』について調べてください。

裕里 2007/02/16(金) 16:34:19
たいちうさん、ありがとうございます。
座標を記憶しておき、InvalidateRectで処理を行うことにしました。
助かりました。ありがとうございます。

『ダブルバッファリング』ちょっと調べてみますね。(^_^

裕里 2007/02/21(水) 18:45:29
度々申し訳ありません。
少し問題がありまして、ご教授いただけないでしょうか。

コントロール選択時に隅に■を表示しているのですが、ダイアログの背景色が
黒色なので、周りを白で囲んだ■にて表示しておりました。
そこで、消去するときに背景色が黒なのを良いことに周りの白の部分を黒に塗りつぶし
てきえているようように描画してみせかけてました。

コントロールが単体の場合は、問題ないのですが、複数重なりあった場合に、
その黒い部分がコントロール上に描画されてしまいました。

このような場合、どう対応すべきなのでしょうか?
もともとの■部分を塗りつぶすのではなくて、消去したいのです。

申し訳ありませんが、ご教授ください。

SundayProgramer 2007/02/22(木) 00:03:27
ChildWindowFromPoint で、■の下の、コントローラのポインタを持ってきて、Invalidate 
で、試してみてください。

いまさらかもしれないけど、コントローラを動的サブクラス化して、
移動の時は、
lParam = MAKELPARAM(point.x, point.y);
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, lParam);
で、リサイズは
lParam = MAKELPARAM(point.x, point.y);
PostMessage(WM_NCLBUTTONDOWN, HTLEFT/*など*/, lParam);
で、いけるような気がします。

この方法なら、デフォルトで点線を書いてくれるので、描いたり消したりもいらないし、
マウス押下をオーバーライドしてやれば、コマンドメッセージも飛んでこないので、やりやすい
かな、とふと思ったりもします。

通りすがり改め、SundayProgramer

裕里 2007/02/22(木) 09:47:28
何度も質問になってしまいまして、申しわけありません。
『コントローラを動的サブクラス化』という方法がわかりません。
どのようにすればよいのでしょうか

裕里 2007/02/22(木) 10:12:03
下記内容でコールしているのですがアサーションが出てしまいます。
bRet = pdata->pWnd->SubclassDlgItem(pdata->nID,this);


pdata->nIDはCreateしたときのコントロールIDです。

たいちう 2007/02/22(木) 10:34:09
> 『コントローラを動的サブクラス化』という方法がわかりません。

http://www.athomejp.com/goldfish/mfc/button/clrbutton.asp

この方法で、ボタンの見た目を自由に変えることができますので、
おそらくこれを指しているのではないかと。


あと、PATIOさんの『2007/02/15(木) 12:29:00』の投稿を
読み返してほしいのですが、とりあえず、デザインモードの
ことだけを考えてみてはどうでしょうか?

デザインモードのボタンは、クリックに応答する必要もなく、
ただ大きさや配置があるだけの絵で十分ではないですか?
デザインモードで作成した座標データを元に、
実行モードでコントロールを作成・配置するほうが良さそう。

この方針に従うならば、最終目標までの道筋は次のようなものかと。

1.ダイアログに複数の四角を書く。
2.そのうちの1つにフォーカスを持たせる。
    (フォーカスを持った場合の表示と、フォーカスの移動方法が必要)
3.フォーカスを持つ四角をドラッグで移動
4.フォーカスを持つ四角の辺付近で、マウスカーソルを変更
5.マウスカーソルが変わっているときに、ドラッグで大きさ変更
6.四角の追加・削除等を実装
7.座標データを何らかの形で保存

これで機能的にはデザインモードが完成。
もっと作りこむ余地はいくらでもありますが。

8.座標データを元に、コントロールの作成・配置

実行モードが正常に実行できることの確認も大変そうだ。


# 以前も書きましたが、本当にやる必要がありますか?
# 私の力量だと2週間ほどの作業をイメージしてますが。
# 仕事でするならば、別の方法も検討すべきですが、
# 勉強のためならば全然OKです。
# 途中で挫折したとしても勉強になるでしょうが、
# できれば、デザインモードから実行モードまで、
# ちゃんと動くものを完成させてください。
# まずは、ボタン1つの大きさを変えるだけでも良いので。

裕里 2007/02/22(木) 10:57:34
たいちろうさん、お返事ありがとうございます。

各コントロールに構造体で座標や選択済みとかの情報を持たせて、
現状では下記までは完成しており


1.ダイアログに複数の四角を書く。
2.そのうちの1つにフォーカスを持たせる。
    (フォーカスを持った場合の表示と、フォーカスの移動方法が必要)
3.フォーカスを持つ四角をドラッグで移動
4.フォーカスを持つ四角の辺付近で、マウスカーソルを変更
5.マウスカーソルが変わっているときに、ドラッグで大きさ変更
6.四角の追加・削除等を実装
7.座標データを何らかの形で保存


+
・複数選択された場合の移動、
・複数選択された場合にリサイズを許可しない
等はできるようになりました。簡単なデザインモードの動作はできているのですが、

後は、細かい描画で、コントロールが重なった場合に■が残ってしまったりする場合の
処理にてこずってます。


今後の予定としましては、てこずっている内容の修正。
また、複数選択時にコントロールに優先付けをして、■の外枠と□の外枠の
区別を行い、優先コントロールを元にした位置あわせ、リサイズを考えております。


つきまして、教えていただきたいのですが、VCというかwindowというか、説明しずらい
のですが、機能として、コントロールを押下した場合に■を描画でなくて、出す方法が
あるのでしょうか?

SundayProgramerさんの話では、サブクラス化することにより・・・・とあります。
実装上、■を描画せずに機能的に出す方法はあるのでしょうか。

申し訳ありません。ご教授ください。

たいちう 2007/02/22(木) 11:53:15
> 各コントロールに構造体で座標や選択済みとかの情報を持たせて、
> 現状では下記までは完成しており

一応書きますが、私の書いた道筋はデザインモードでコントロールを
使わない場合です。


> SundayProgramerさんの話では、サブクラス化することにより・・・・とあります。
> 実装上、■を描画せずに機能的に出す方法はあるのでしょうか。

私は無いと思いますが、知らないだけかもしれません。
SundayProgramerさんの真意については私では判りません。
ご本人か、上級者のレスをお待ち下さい。
(投稿されたソースコード等から自明なのかもしれませんので)
おそらく、そのようなことができるという趣旨ではないと思います。


> 後は、細かい描画で、コントロールが重なった場合に■が残ってしまったりする場合の
> 処理にてこずってます。

消す処理が必要ないのではないのでしょうか?

フォーカスの移動などで、■が不要になったときは、そのコントロールの
フォーカスフラグをfalseにして、Dialogに対してInvalidate。
OnPaintでは以前の状態など気にせず、フラグが立っていれば■を描き、
立ってなければ何もしない。

もしInvalidateRectを使うならば、コントロールよりも一回り大きい領域を
指定してください。

裕里 2007/02/22(木) 12:09:12
回答ありがとうございます。
OnPaint内での処理で問題なくなりました。

今までOnPaintを作成してませんでした。
これで、複数の問題が解決しました。

裕里 2007/02/22(木) 16:12:39
本当に何度も申し訳ありません(>_<)

今回、エディット中のコントロールをツーリングバーより、右寄せや左寄せを
行いたいと思い、ツールバーを作成したのですが、コントロールが選択されるまで
ツールバーのボタンを押下できないようにしたいのですが、いつもアクティブな
状態のままで対応に困っています。ツールバーのボタンの制御は、どのように
行えばよいのでしょうか。
宜しく、ご教授ください。

たいちう 2007/02/22(木) 16:35:23
ON_UPDATE_COMMAND_UI について調べてください。

裕里 2007/02/22(木) 16:46:58
たいちうさん、ありがとうございます。
なんとかツールバーの制御はできました。
ありがとうございます。

SundayProgramer 2007/02/23(金) 00:23:41
>bRet = pdata->pWnd->SubclassDlgItem(pdata->nID,this);

コントロールの動的サブクラス化はそんな感じですが、微妙に使い方が違います。やり方は、ま
ずダイアログクラスに、CButtonなどのメンバ変数を作って、(仮にm_Buttonとします。
m_Button は Create せず、宣言するだけです。)後は、必要な時に、

m_Button.SubclassDlgItem(pdata->nID,this);

とすると、m_Button を通して、ボタンとのやり取りができるようになります。
ClassWizerd の コントロールへのメンバ変数の割付を、手動でやるみたいな感じです。

前の書き込みで誤解を与えてしまったみたいなんですが、これを少し工夫すると、点線の描画等
は出来るんですが、■までは出来ません。

一応コードはこんな感じです。

// 編集用の自作クラス CWnd 継承にしておくと、ボタン以外でもサブクラス化できる。
class CEditedCtrl : public CWnd
{
  …
};

// WM_NCHITTESTのハンドラ
UINT CEditedCtrl::OnNcHitTest(CPoint point) 
{
    // TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォル
トの処理を呼び出してください
    return HTCAPTION;  // ウィンドウ領域全体をタイトルバー化 
    //return CWnd::OnNcHitTest(point);
}

class CXXDlg : public CDialog
{
 …
 CEditedCtrl m_EditedCtrl;
}

後は例の OnSetCursor の WM_MOUSEMOVE のタイミングでで、 

if(m_EditedCtrl.GetSafeHwnd() != pWnd->GetSafeHwnd() && pWnd != this){
  if(m_EditedCtrl.GetSafeHwnd() != NULL){
    m_EditedCtrl.UnsubclassWindow();
  }
  if(pWnd->GetSafeHwnd() != NULL){
    m_EditedCtrl.SubclassWindow(pWnd->GetSafeHwnd());
  }
}

とするだけで、移動とコマンドメッセージのブロックをやってくれます。一度に一つしかサブク
ラス化できないことと、this はサブクラス化できないので気をつけてください。

編集系は、CEditedCtrl に任せてしまうとコードもすっきりするのかな、と思います。
ただ、随分書き換えも必要だろうし、あちらを立てればこちらが立たずって事もありうるので参
考までに。

裕里 2007/02/23(金) 10:00:00
SundayProgramerさん、ありがとうございます。
早速、参考にさせていていただきます。(^_^)

PATIO 2007/02/23(金) 12:13:42
これだと今やろうとしている事が全て終わらないと
このスレッドが閉じない事になりそうです。
できれば、質問は小分けにして一問一答のような形式で
スレッドを起こした方が後でスレッドを見る人には良いと思います。
(要は、一スレッドは一質問にした方が良いと言う話です)

裕里 2007/02/23(金) 12:51:03
[[解決]]
了解です。一度閉めさせていただきますね。

古いスレッドには追加発言できません。