ページ 11

ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月06日(水) 15:56
by jacoby
BCC Developer 1.2.21 (borland C++)でC++のプログラムを書いています。(Windows Me)

ボタンタイプのラジオボタン(オプションボタン)をオーナー描画して作りたいと下記のようなコードを書いたのですが、ラジオボタンとして正しく動きません。
現象としては「ラジオボタン(同一グループ)以外の他のコントロールを一度触った後に再び何れかのラジオボタンを押すと、直前に押していたラジオボタンが凹んだままで、凸に戻らない」
というものです。

何故そうなるのかについてはある程度明らかなのですが、
(下記のコード中ではオーナーウインドウのコールバック・プロシージャ内で
WM_DRAWITEMを拾ってオーナー描画をしているのですが、そのとき
同一グループ内のあるラジオボタンから別のラジオボタンをクリックしたときには、
LPARAM で渡されるDRAWITEMSTRUCT構造体の情報、
コントロールのIDとlpDraw->itemAction、itemStateの値が次のような順で現れます。

① ボタンを押した時 ---> ID=今回押したボタン  itemAction=ODA_SELECT   itemState=17(※1)
② 「①」の直後に  ---> ID=前回押していたボタン itemAction=ODA_DRAWENTIRE itemState=0
③ 「①」のボタンを離した時---> ID=今回押したボタン itemAction=ODA_SELECT itemState=16(※2)
(※1 17= ODS_SELECTED(16) | ODS_FOCUS(1) )
(※2 16= ODS_SELECTED(16) )

ここで直前に押されていたボタンを元に戻すのにこのコードでは②のitemAction=ODA_DRAWENTIREを拾った時を利用して行っています。
ところが一度他のコントロール(又は他のウインドウ)を触ってからラジオボタンにもどると
②が現れず、①→③と直接移ります。これにより前に押していたボタンが「押されっぱなし」になってしまうものと思われます。

ただしそのことが分かっても、いざそれを直そうとすると一体どのようにしていいのか分からず。直前のボタンのデバイス・コンテキストや描画用のRECT構造体など一切合切保存して、他のコントロールから移ってきたかどうかを判定して、、というのもどうなのかなという気もしますし。。。
このようなラジオボタンのオーナー処理について、良い方法がありましたらご教授下さい。

コード:

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hInstance;
HWND hMainWnd;
HFONT hFont;
HBRUSH h3DFaceBrush;

HWND hRadioButton[15];
const RadioButton1=100;
long oldRadioButtonID;

//
//●コールバック・プロシージャ(ラジオボタンのオーナーウインドウ)
//
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 
    switch (msg) {

        case WM_CREATE:{
            hInstance=((CREATESTRUCT *)lp)->hInstance;
            hFont=CreateFont(-12,0,0,0,400,0,0,0,128,3,2,1,32,"MS Pゴシック");
            hMainWnd=hWnd;

            SetWindowPos(hMainWnd,NULL,0,0,296,316, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
            SetWindowText(hMainWnd,"ラジオボタン・テスト");

            //-----------------------------
            //16個(4×4)のラジオボタン作成
            //-----------------------------

            long radioButton_offsetXA=16,radioButton_offsetYA=16;
            long radioButtonXV=64,       radioButtonYV=64;
            long radioButtonXA,          radioButtonYA;
            int i;

            for (i=0; i<16; i++){
             radioButtonXA=radioButton_offsetXA+radioButtonXV*(i & 3);
             radioButtonYA=radioButton_offsetYA+radioButtonYV*(i >> 2);
             hRadioButton[i]=CreateWindowEx(0x00000000,"BUTTON","",0x50001309 | BS_OWNERDRAW, radioButtonXA, radioButtonYA, radioButtonXV, radioButtonYV, hMainWnd, (HMENU)(RadioButton1+i),hInstance,0);
             SendMessage(hRadioButton[i],WM_SETFONT,(WPARAM)hFont,0);
            }
	    
            ShowWindow(hMainWnd,SW_SHOW);

	    }return TRUE;

	case WM_DRAWITEM:{

            //---------------------------
            //ラジオボタンのオーナー描画
            //---------------------------

            LPDRAWITEMSTRUCT lpDraw = (LPDRAWITEMSTRUCT)lp;

            if (lpDraw->itemAction & ODA_SELECT){ //アイテム動作が「選択」なら以下の処理、そうでないなら9行下「else②」へ 
              if (lpDraw->itemState & ODS_FOCUS){ //アイテム状態が「フォーカス取得」なら以下の処理
                if (lpDraw->CtlID != oldRadioButtonID){ //今回のラジオボタンが直前に押していたラジオボタンでないならそれを凹ませる(IDで判定)
                  DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_SUNKEN, BF_RECT);
                  oldRadioButtonID=lpDraw->CtlID; //今回のラジオボタンのIDを保存
                  return TRUE;
                }
              }
            }
            else {  //<-----else② アイテム動作が「選択」以外ならここに来るが、実際には「ODA_DRAWENTIRE」の時のみ。
                    //主にコントロールが他のウインドウとの重なりなどで一旦消えてしまった後などに来る。
                    //何故か「ODA_FOCUS」は検出されない。このif文の頭でODA_FOCUSによる判定ではなく、「ODA_SELECT」を使用しているのは単にそれだけの理由。
                    //ここでは何故かODA_SELECT=2 と ODA_DRAWENTIRE=1 しか現れず ODA_FOCUS=4 が現れない。

              if (lpDraw->CtlID==oldRadioButtonID){  //今回のIDが直前に押していたものと同じなら「このボタンは凹んでいる」として凹み描画
                DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_SUNKEN, BF_RECT);
              }
              else {                                 //今回のIDが直前に押していたものと違うなら「このボタンは凸」として凸描画
                DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_RAISED, BF_RECT);
              }
              return TRUE;
            }

            }break;

        case WM_DESTROY:
            DeleteObject(hFont);
            DeleteObject(h3DFaceBrush);
            PostQuitMessage(0);
            return TRUE;

        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return FALSE;
}

Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月06日(水) 16:52
by ISLe
標準コントロールを使うときは、きちんとウインドウズの流儀に沿って、ダイアログベースのアプリケーションとして作成しないといろいろ不具合が発生します。
  • ウインドウクラスを作成するとき、cbWndExtraメンバにDLGWINDOWEXTRA(定数)を指定して領域確保してください。
  • メッセージループにIsDialogMessageを挟んでダイアログメッセージがディスパッチされるようにしてください。
  • ウインドウプロシージャから呼び出すデフォルトウインドウプロシージャをDefDlgProcにしてください。
  • WM_CLOSEメッセージで明示的にDestroyWindowを呼び出すようにしてください。
これで期待どおりに動くと思いますが、期待以上に動いてしまう部分があるかもしれません。

Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月06日(水) 20:23
by jacoby
ISLeさん返信ありがとうございます。
書いていなくて申し訳ありません。プログラムはご指摘の通りダイアログベースの
プロジェクトとして製作しました。上記コードは質問内容の該当部分の抜粋です。
 当初は全文を貼り付けようとしたのですがそうすると、「投稿が禁止されている単語が含まれています」と弾かれてしまって出来なかったんです。ただしコードはBCC Developerで動作しているもののそのままコピーなので、どれが不正な単語か分からずとりあえず説明に必要のない部分をどんどん切っていって結果あのような形になってしまいました。
改めて全文を貼り付けてみたいのですが、、、
、、
やっぱり弾かれます。
ちょっと色々試してみたのですがとりあえず分からないなりに不正な単語と思われる
箇所を大文字で書き直しました。申し訳ないのですが実行時にはそこを小文字に
変えてみてください。
(●ウィンドウ・クラスの登録 "ATOM InitWindowClass()"内)

 ところで、もし「これで期待どおりに動くと思いますが、期待以上に動いてしまう部分があるかもしれません。」ということは、オーナードローの部分はこれで正しいということでしょうか?
こちらの環境ではやはり質問文のような不具合が出ます。


以下のコードはこのままBCC Developerにて、新しいプロジェクトの***.cppファイルにコピペして、「●ウィンドウ・クラスの登録 "ATOM InitWindowClass()」内の一部大文字部分を半角に変換していただければ単体で動くと思います。

コード:

#include <windows.h>
#include <stdlib.h>
#include <commctrl.h>


LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitWindowClass(HINSTANCE hInst, WNDPROC WndProc, LPCTSTR szClassName, LPCTSTR szMenuName, HBRUSH hBrush);
BOOL CreateWindow_Main(HINSTANCE, int);

HINSTANCE hInstance;
HWND hMainWnd;
HFONT hFont;
HBRUSH h3DFaceBrush;

HWND hRadioButton[15];
const RadioButton1=100;
long oldRadioButtonID;

//
//●コールバック・プロシージャ
//
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 
    switch (msg) {

        case WM_CREATE:{
            hInstance=((CREATESTRUCT *)lp)->hInstance;
            hFont=CreateFont(-12,0,0,0,400,0,0,0,128,3,2,1,32,"MS Pゴシック");
            hMainWnd=hWnd;

            SetWindowPos(hMainWnd,NULL,0,0,296,316, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
            SetWindowText(hMainWnd,"ラジオボタン・テスト");

            //-----------------------------
            //16個(4×4)のラジオボタン作成
            //-----------------------------

            long radioButton_offsetXA=16,radioButton_offsetYA=16;
            long radioButtonXV=64,       radioButtonYV=64;
            long radioButtonXA,          radioButtonYA;
            int i;

            for (i=0; i<16; i++){
             radioButtonXA=radioButton_offsetXA+radioButtonXV*(i & 3);
             radioButtonYA=radioButton_offsetYA+radioButtonYV*(i >> 2);
             hRadioButton[i]=CreateWindowEx(0x00000000,"BUTTON","",0x50001309 | BS_OWNERDRAW, radioButtonXA, radioButtonYA, radioButtonXV, radioButtonYV, hMainWnd, (HMENU)(RadioButton1+i),hInstance,0);
             SendMessage(hRadioButton[i],WM_SETFONT,(WPARAM)hFont,0);
            }
	    
            ShowWindow(hMainWnd,SW_SHOW);

	    }return TRUE;

	case WM_DRAWITEM:{

            //---------------------------
            //ラジオボタンのオーナー描画
            //---------------------------

            LPDRAWITEMSTRUCT lpDraw = (LPDRAWITEMSTRUCT)lp;

            if (lpDraw->itemAction & ODA_SELECT){ //アイテム動作が「選択」なら以下の処理、そうでないなら下「else②」へ 
              if (lpDraw->itemState & ODS_FOCUS){ //アイテム状態が「フォーカス取得」なら以下の処理
                if (lpDraw->CtlID != oldRadioButtonID){ //今回のラジオボタンが直前に押していたラジオボタンでないならそれを凹ませる(IDで判定)
                  DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_SUNKEN, BF_RECT);
                  oldRadioButtonID=lpDraw->CtlID; //今回のラジオボタンのIDを保存
                  return TRUE;
                }
              }
            }
            else {  //<-----else② アイテム動作が「選択」以外ならここに来るが、実際には「ODA_DRAWENTIRE」の時のみ。
                    //主にコントロールが他のウインドウとの重なりなどで一旦消えてしまった後などに来る。
                    //何故か「ODA_FOCUS」は検出されない。このif文の頭でODA_FOCUSによる判定ではなく、「ODA_SELECT」を使用しているのは単にそれだけの理由。
                    //ここでは何故かODA_SELECT=2 と ODA_DRAWENTIRE=1 しか現れず ODA_FOCUS=4 が現れない。

              if (lpDraw->CtlID==oldRadioButtonID){  //今回のIDが直前に押していたものと同じなら「このボタンは凹んでいる」として凹み描画
                DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_SUNKEN, BF_RECT);
              }
              else {                                 //今回のIDが直前に押していたものと違うなら「このボタンは凸」として凸描画
                DrawEdge(lpDraw->hDC, &lpDraw->rcItem, EDGE_RAISED, BF_RECT);
              }
              return TRUE;
            }

            }break;

        case WM_DESTROY:
      DeleteObject(hFont);
            DeleteObject(h3DFaceBrush);
            PostQuitMessage(0);
            return TRUE;

        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return FALSE;
}

//------以下は質問内容とは直接関係無いウインドウ作成関係------

//
//●WinMain
//
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    h3DFaceBrush=CreateSolidBrush(GetSysColor(COLOR_3DFACE));
    if (!InitWindowClass(hCurInst,MainWndProc,"Class_MainWnd","IDM_MainMenu",h3DFaceBrush))
        return FALSE;
    if (!CreateWindow_Main(hCurInst, nCmdShow)) 
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

//
//●ウィンドウ・クラスの登録
//
ATOM InitWindowClass(HINSTANCE hInst, WNDPROC WndProc, LPCTSTR szClassName, LPCTSTR szMenuName, HBRUSH hBrush)
{
   WNDCLASSEX wc; 
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0; //CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc; //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst; //インスタンス
    wc.hIcon = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hCursor = (HCURSOR)LoadImage(NULL,
        MAKEINTRESOURCE(IDC_ARROW),
        IMAGE_CURSOR,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);

    wc.hbrBackground = hBrush;
    wc.lpszMenuName = (LPCSTR)szMenuName;//"IDM_MAINMENU";
    wc.lpszClassName = (LPCSTR)szClassName;
    wc.hIconSm = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);

    return (RegisterClassEx(&wc));

 }

//
//●ウィンドウの生成
//
BOOL CreateWindow_Main(HINSTANCE hInst, int nCmdShow)
{
HWND hWnd;

    hWnd = CreateWindowEx(0,
            "Class_MainWnd",
            "Test", 
            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            480,    //幅
            360,    //高さ
            NULL, //親ウィンドウのハンドル、親を作るときはNULL
            NULL, //メニューハンドル、クラスメニューを使うときはNULL
            hInst, //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;

    return TRUE;
}


Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月07日(木) 03:53
by ISLe
指摘した内容がまったく適用されていないようですが。
ダイアログメッセージを処理しなければダイアログベースのアプリケーションとは言えません。
標準コントロールを貼りつけただけの、ふつうのウインドウアプリケーションです。

期待以上の動作とは、ウインドウズの流儀に沿った動作のことです。
ダイアログメッセージを処理すれば、オーナードローは期待どおりに動作するようになりますが、それ以外にも標準的なUIが適用されるようになります。
それは意図しない動作かもしれないですが、抵抗すればさまざまな不具合に悩まされることになると思います。

Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月08日(金) 19:50
by jacoby
ISLeさん、ありがとうございます。返信が遅れてしまって申し訳ありません。
「ダイアログベース」のアプリケーションとは
ダイアログがベースのウインドウのアプリケーションのことなんですね。
http://www.river.sannet.ne.jp/yuui/WinD ... seApp.html

上の自分のプログラムは全くそうなっていません。
上のコードのMainWndは元々自分のアプリケーションプログラムの
モーダル・ダイアログ部分で、それを投稿時に単体で動作するように
抜き出してまとめたものだったので、それでいいのかなと思ってしまいました。
すみません。

それで、上のプログラムをダイアログ・ベースに変えている最中なのですが
まだ、肝心のウインドウを表示出来ずにいます。
とりあえず、ダイアログベースのウインドウをまず表示出来るようにと思い
それ以外の部分は削って、下のようなコードを書いたのですが、

コード:

#include <windows.h>
#include <stdlib.h>
#include <commctrl.h>

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitWindowClass(HINSTANCE hInst, WNDPROC WndProc, LPCTSTR szClassName, LPCTSTR szMenuName, HBRUSH hBrush);

HWND hMainWnd;
HBRUSH h3DFaceBrush;

//
//●ウィンドウプロシージャ
//
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 
    switch (msg) {

        case WM_CLOSE:
            DestroyWindow(hWnd);
            return TRUE;
	
        case WM_DESTROY:
            DeleteObject(h3DFaceBrush);
            PostQuitMessage(0);
            return TRUE;

        default:
            return DefDlgProc(hWnd,msg,wp,lp);
    }
    return FALSE;
}


//------以下はウインドウ作成関係------

//
//●WinMain
//
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    h3DFaceBrush=CreateSolidBrush(GetSysColor(COLOR_3DFACE));
    
    if (!InitWindowClass(hCurInst,MainWndProc,"DIALOG","",h3DFaceBrush))
        return FALSE;
        
    hMainWnd=CreateDialog(hCurInst,"IDD_DIALOG1", NULL, (DLGPROC)MainWndProc);
    if (!hMainWnd){
      MessageBox(0,"ウインドウが作れませんでした","エラー",0);
      return 0;
    }
    ShowWindow(hMainWnd,SW_SHOW);    
  
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            if (!IsDialogMessage(hMainWnd,&msg)){
                   TranslateMessage(&msg);
                   DispatchMessage(&msg);
            }
        }
    }
    
    return (int)msg.wParam;

}

//
//●ウィンドウ・クラスの登録
//
ATOM InitWindowClass(HINSTANCE hInst, WNDPROC WndProc, LPCTSTR szClassName, LPCTSTR szMenuName, HBRUSH hBrush)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;    //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = DLGWINDOWEXTRA;
    wc.hInstance = hInst;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground = hBrush;
    wc.lpszMenuName = NULL;
    wc.lpszClassName =szClassName;
 
    return (RegisterClassEx(&wc));
}

//
//リソース・ファイル "OwnerDrawTest.rc" BCC Formにて作成
//
//-----------------------------------------
#include	"ResOwnerDrawTest.h"

//----------------------------------
// ダイアログ (IDD_DIALOG1)
//----------------------------------
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 202, 135
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CAPTION "Form"
FONT 8, "MS 明朝"
{
}

//
//ヘッダー・ファイル "ResOwnerDrawTest.h"
//
//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_DIALOG1
//
//END

ちょっとまだ、原因が分からずにいます。
またお気付きのことがありましたら教えてください。
返信ありがとうございました。

Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月08日(金) 21:13
by ISLe
もう一度書きますが、最初に投稿されたコードに対して、以下の内容を適用してください。
  • ウインドウクラスを作成するとき、cbWndExtraメンバにDLGWINDOWEXTRA(定数)を指定して領域確保してください。
  • メッセージループにIsDialogMessageを挟んでダイアログメッセージがディスパッチされるようにしてください。
  • ウインドウプロシージャから呼び出すデフォルトウインドウプロシージャをDefDlgProcにしてください。
  • WM_CLOSEメッセージで明示的にDestroyWindowを呼び出すようにしてください。
CreateDialogを使うかどうかは、ウインドウの外見上の違いでしか無く、本件とはまったく関係ありません。

(追記)修正したソースファイルを添付しました。

Re: ラジオボタン(オプションボタン)のオーナー描画について

Posted: 2011年4月11日(月) 01:23
by jacoby
返信ありがとうございます。
ソースを修正して頂いて感謝しています。
基本的なことでまだ分かっていないことも多いのですが
頂いたソースを参考に少しずつですが進めていければと思っています。
ありがとうございました。