プロが教える店舗&オフィスのセキュリティ対策術

作成中のアプリもだいぶ必要機能を網羅出来て来て
よーし、もうちょっとだ!ってとこに来て
コンボボックスのドロップダウンが「ほとんど表示されてない」事に気付きました。

なんでやのんということで調べてみると

どうやらCreateWindowExとかリソースとかで指定する「高さ」が

コンボボックスでCBS_DROPDOWNLISTスタイル込みの場合

ドロップダウンを表示させたときのドロップダウンリストの高さを含むように
予約領域先に作っておかなければいけない、とのこと

えー、クリックすると表示になる割に 「内部的に別々で自動的に高さ計算」 とかになってるんじゃないのー?


ということで
またしてもMicrosoftちゃんにしてやられたって気分ですが


しょうがないのでちゃっちゃとクラス化してみました。
(ていうか、なんで他のコントロールはほとんど独自クラスにしてあるのに、コンボボックスはクラス化してなかったんだろうと、小一時間(ry))

なお、現状ではMFCは使えません(w | orz)


あ、たぶん
TcsLiteral → const TCHAR* const
szt → size_t
以外のtypedefやdefineは見れば元が何なのか分かる(か、分かんなくても問題ないはず)
と思うので省略しまふ(お)

ヘッダ

#pragma once

class myComboBox {

HWND wnd;
szt num;

public:

myComboBox( HWND hw, int x, int y, int cx );
~myComboBox();

BOOL Enable( BOOL b ) const { return EnableWindow( wnd, b ); }
Void Select( Dword i ) const { SendMessage( wnd, CB_SETCURSEL, (WPARAM)i, 0 ); }
Int GetIndex() const { return (Int)SendMessage( wnd, CB_GETCURSEL, 0, 0 ); }
Void Setup() const;

Void AddString( TcsLiteral s ){
SendMessage( wnd, CB_ADDSTRING , 0 , (LPARAM)s );
++num;
}

Void AddStrings( TcsLiteral* const s, const szt num_ ){
for ( szt i = 0; i < num_; ++i ) SendMessage( wnd, CB_ADDSTRING , 0 , (LPARAM)s[i] );
num += num_;
}

};

ソース

#include "StdAfx.h"
#include "myComboBox.h"
#include "FontLibrary.h"

myComboBox::myComboBox( HWND hw, int x, int y, int cx ) : num(0) {

wnd = CreateWindowEx(0, _T("COMBOBOX"),null ,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST ,
x, y , cx, 0, hw, null, HINST::me, null);

}

myComboBox::~myComboBox(){
DestroyWindow( wnd );
}

Void myComboBox::Setup() const {

RECT rc;
GetClientRect( wnd, &rc );

int cy = (int)SendMessage( wnd, CB_GETITEMHEIGHT, -1, 0 );
cy += (int)SendMessage( wnd, CB_GETITEMHEIGHT, 0, 0 ) * num;
cy += GetSystemMetrics(SM_CYEDGE) * 2;
cy += GetSystemMetrics(SM_CYEDGE) * 2;

SetWindowPos( wnd, null, 0, 0, rc.right, cy, SWP_NOMOVE|SWP_NOZORDER );

SendMessage( wnd, CB_SETCURSEL, 0 , 0 );
SendMessage( wnd, WM_SETFONT, (WPARAM)FontLibrary::Default, True );

}

使い方の例はこんな感じです。

wnd = CreateWindowEx( ~




combo_measden = new myComboBox( wnd, 50, 200, 60 );

TcsLiteral c[] = { _T("1"), _T("2"), _T("4"), _T("8"), _T("16"), _T("32") };
combo_measden->AddStrings( c, sizeof c / sizeof(c[0]) );
combo_measden->Setup();

解放時


/* Windows的には「HWNDの、子ウインドウ」のみについて言えば親ウインドウのDestroyのときに同時に自動解放してくれるようですが、どっちみちcombo_measdenポインタはdeleteかけないといけないので */
delete combo_measden;
DestryWindow( wnd );


で、今回何が知りたいのかというと

このSetup関数の中の

int cy = (int)SendMessage( wnd, CB_GETITEMHEIGHT, -1, 0 );
cy += (int)SendMessage( wnd, CB_GETITEMHEIGHT, 0, 0 ) * num;
cy += GetSystemMetrics(SM_CYEDGE) * 2;
cy += GetSystemMetrics(SM_CYEDGE) * 2;

SetWindowPos( wnd, null, 0, 0, rc.right, cy, SWP_NOMOVE|SWP_NOZORDER );

この調整部分です。

こんでXP以降のWindowsは全部OKですかね?
XPはクラシックでもXP用のVisualスタイルでもおkっぽいですが。

その他の突っ込みどころもあれば募集致します。

A 回答 (4件)

> LOGFONTのlfHeightが「XP以降のどれについても」そのまんまコンボボックスの高さとして使えるかどうか



> っていうのは、MSDN確実な問題って言う表記はありましたでしょうか?

ないでしょう。
画面の解像度(ディスプレイの画素数だけでなく、DPI値を含む)にも影響されるでしょうし。

リソースエディタを使う場合も含めて、最初に(見た目など適当な数値で)高さを決めておけば、行が中途半端に表示されるとか空白の行がいくつも表示されるようなことはないようにWindowsが調整してくれるので、環境によって表示される行数が多少違っても問題ないのであれば、任せちゃったほうが楽だというだけです。
厳密にどの環境でも同じ行数を表示したいということなら、提示されたコードでいいんじゃないでしょうか。
    • good
    • 0
この回答へのお礼

いや~
細かく何度もありがとうございます♪

全く同じ見解のようで、安心しました。


ちょっと貸してもらって、試すことが出来ましたよ

結果、やった範囲の全てのテーマ(デザイン)などで、XPでもVistaでも7でも

下のコードで全部思い通りの行数表示になることが分かりました。


もちろん
もし仮にずれる環境があったとしてもWS_VSCROLLを指定しておけば


計算した高さ > 指定されるべきぴったりの高さ

であれば、表示数が多くなるだけか
限界まで表示されてれば
自動的に縮小されて表示される仕様により、問題なし。

逆に


計算した高さ < 指定されるべき高さ

であれば、意図してないとこでスクロールバーが表示されるか
表示される数が少なくなるけども

スクロールはできるのでドロップダウンリスト上での選択の可否ということであれば
問題なし。
(WS_VSCROLL指定しとくと最小以下でも1個は表示されるっぽい(?))


ということで

また
CB_GETITEMHEIGHT
は、やはりいかにも専用のメッセージっぽく思うので

このコードで行こうと思います。
(少しだけ変えたので、現時点での、調整後の最終コードを載せておきます)


////////myComboBox.h////////

#pragma once

class myComboBox {

HWND wnd;
szt num;


public:

myComboBox( HWND hw, int x, int y, int cx,
DWORD style_ = WS_CHILD|WS_VSCROLL|CBS_DROPDOWNLIST );

~myComboBox();


BOOL Enable( BOOL b ) const { return EnableWindow( wnd, b ); }
Void Select( Dword i ) const { SendMessage( wnd, CB_SETCURSEL, (WPARAM)i, 0 ); }
Int GetIndex() const { return (Int)SendMessage( wnd, CB_GETCURSEL, 0, 0 ); }
Void Setup( Dword index_to_select = 0, szt max_num_to_show = 0 ) const;

Void AddString( TcsLiteral s ){
SendMessage( wnd, CB_ADDSTRING , 0 , (LPARAM)s );
++num;
}

Void AddStrings( TcsLiteral* const s, const szt num_ ){
for ( szt i = 0; i < num_; ++i ) SendMessage( wnd, CB_ADDSTRING , 0 , (LPARAM)s[i] );
num += num_;
}

};


////////myComboBox.cpp////////

#include "StdAfx.h"
#include "myComboBox.h"
#include "FontLibrary.h"

myComboBox::myComboBox( HWND hw, int x, int y, int cx, DWORD style ) : num(0) {

wnd = CreateWindowEx( 0, _T("COMBOBOX"), null,
style, x, y , cx, 0, hw, null, HINST::me, null);

}

myComboBox::~myComboBox(){ DestroyWindow( wnd ); }

Void myComboBox::Setup( Dword i, const szt maxnum_ ) const {

SendMessage( wnd, WM_SETFONT, (WPARAM)FontLibrary::Default, False );

RECT rc;
GetWindowRect( wnd, &rc );

const szt n_ = ( maxnum_ && maxnum_ < num ) ? maxnum_: num;

int cy = (int)SendMessage( wnd, CB_GETITEMHEIGHT, -1, 0 );
cy += (int)SendMessage( wnd, CB_GETITEMHEIGHT, 0, 0 ) * n_;
cy += GetSystemMetrics(SM_CYEDGE) * 4;

SetWindowPos( wnd, null, 0, 0, rc.right-rc.left, cy, SWP_NOMOVE|SWP_NOZORDER|SWP_SHOWWINDOW );

Select( i );

}



(もしまたなんか変な個所が見つかったら直すと思いますが)
どうもありがとうございました♪

お礼日時:2012/02/22 16:22

調整される方向は、縮小だけです。

勝手にサイズを大きくしたりはしません。
10.5行分の高さを指定して作成すると、10行分の大きさで表示される。選択肢が5つしかなければ、5行分に縮小されるということです。

フォントの高さは、LOGFONT情報を取得すればわかります。

HFONT hFont = FontLibrary::Default;
LOGFONT lf;

ZeroMemory(&lf, sizeof(lf));
GetObject(hFont, sizeof(lf), &lf);

とすると、lf.lfHeightにフォントの高さが取得できます。
    • good
    • 0
この回答へのお礼

どうもどうも

いずれも書いた後で文章見直してみて
同様のことを思いました。

>調整される方向は、縮小だけです。勝手にサイズを大きくしたりはしません。
10.5行分の高さを指定して作成すると、10行分の大きさで表示される。選択肢が5つしかなければ、5行分に縮小されるということです。


はい、なので限界まで表示する分には質問文の方法で問題なかった→限界まで表示する分には余分に確保しておけばいいとなるはず

ただし、スクロールバーを表示させたいとなると話は変わってくる、というわけです。


>フォントの高さは、LOGFONT情報を取得すればわかります。

はい、そうなんですよね。
実際アプリケーションのユーザー設定をファイルに保存したり、取り出したりといったこともやってるので、その時LOGFONTの情報も取り扱っているので
使い方はOKですが

そのことと

LOGFONTのlfHeightが「XP以降のどれについても」そのまんまコンボボックスの高さとして使えるかどうか

っていうのは、MSDN確実な問題って言う表記はありましたでしょうか?

もし一つでも確かめてみないと分かんない環境があれば不確実ってことになりますし

もしコンボボックスの内容を後で変更したい場合って言うのが生じれば
結局Setup関数が必要になるのには変わりないってのがあるんですよね。

お礼日時:2012/02/22 11:37

なんか、そもそも論になっちゃいそうですが、コンボボックスの高さは、スタイルにCBS_NOINTEGRALHEIGHTを付けなければ、



・選択肢が中途半端に表示されないように行の境界に調整される
・項目数が少ない場合は、すべての項目が表示可能な最小の高さに調整される

となってませんでしたか?
なので、例えば選択肢が10を超えたらスクロールするようにしたければ、最初からドロップダウン部分が10行+αになるようにコントロールを作成すればよかったと思いますけど。+αも適当に0.5行分とかで問題ないと。

この回答への補足

>最初からドロップダウン部分が10行+αになるようにコントロールを作成すればよかったと

その場合、リソースでなく、CreateWindowExでやるとしたら
最初から高さを計算して、ってことでしょうか?

そうなるとちょーっとムズイんですよね
FontLibrary::Defaultの内容は
GetStockObject(DEFAULT_GUI_FONT);
なんですが

SendMessage( wnd, WM_SETFONT, (WPARAM)FontLibrary::Default, False );

こいつがあると下の「直したバージョン」のコードの計算手順じゃないと単純化出来ないという問題があったんです。
(質問文の段階の時はたまたま 変更前>変更後だったためOKだったてことかな)

そこさえなんとかできれば簡易化できるかもしれませんが
そうなると余計「環境依存の恐れ」が増えてしまうと思ってしまいます。

補足日時:2012/02/21 17:43
    • good
    • 0
この回答へのお礼

>CBS_NOINTEGRALHEIGHT

はい、そうですね。つけてませんw
付けて試してみましたが

>・選択肢が中途半端に表示されないように行の境界に調整される

こっちに関しては変わったようだけども

>・項目数が少ない場合は、すべての項目が表示可能な最小の高さに調整される

これは、私の環境では少なくとも変わった感じはありません。
SendMessageでWM_SETFONTやるかどうかとも関係ないっぽいです。


やはり
少ない高さに設定して「なんにも手を加えてなかったら」「線しか表示されてなかった」ので質問文のとおりの状況、って事です。


環境で違うんでしょうかね?



まあ、下のコードで万事OKということなら何ら問題はないのですが。

お礼日時:2012/02/21 17:34

コンポボックスのドロップダウン部分の高さは、ダイアログの配置なんかと一緒でデザインの一部だと思いますよ。


元のダイアログの大きさを超えてドロップダウンするとみっとない時もあるから、全項目が一度に表示できなくてもスクロールバー表示で対応したいときもあるし。項目数が固定の時は、全項目が一目でわかるように、多少はみ出してもOKの時もあるし。そもそも、全項目が画面に入りきらない場合もあるし。

あと、コンボボックスの横幅って、ウィンドウ領域とクライアント領域で同じでした?
個人的には、同じだったとしてもGetWindowRectを使うな。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます♪

>コンポボックスのドロップダウン部分の高さは、ダイアログの配置なんかと一緒でデザインの一部だと思いますよ。

なるほど。
Expressだとリソースエディタが使えないので.rcファイルに数値を打ち込むか
最終的にはほとんどはAPI呼び出しに書き変えるという手順で作っていたので
そういう意識がありませんでしたが

そっちからいってれば迷いがなかったかもしれませんね。



>元のダイアログの大きさを超えてドロップダウンするとみっとない時もあるから、全項目が一度に表示できなくてもスクロールバー表示で対応したいときもあるし。項目数が固定の時は、全項目が一目でわかるように、多少はみ出してもOKの時もあるし。そもそも、全項目が画面に入りきらない場合もあるし。


なるほど!
「え?コンボボックスでのドロップダウンリストでの縦のスクロールバーってどうやって表示できるんだろう」と思って色々いじってたら

ウインドウスタイルに
WS_VSCROLL
を加えつつ

myComboBox::myComboBox( HWND hw, int x, int y, int cx ) : num(0) {

wnd = CreateWindowEx(0, _T("COMBOBOX"),null ,
WS_CHILD | WS_VSCROLL | WS_VISIBLE | CBS_DROPDOWNLIST ,
x, y , cx, 0, hw, null, HINST::me, null);

}



高さを「全部は収まらない」ようにする
と出来るって事ですね?(一応確認)

※あるいはCBS_DISABLENOSCROLLが追加してあれば全部収まる高さがあっても無効状態で表示になりますが


一昨日までの段階ではWS_VSCROLLを付けてない状態でしかも高さが1つ分も表示出来ない指定だったので「線」しか出てなかったので「なんじゃこりゃ」な状態でしたが



>あと、コンボボックスの横幅って、ウィンドウ領域とクライアント領域で同じでした?
>個人的には、同じだったとしてもGetWindowRectを使うな。


それ私も最初思ったのですが
どこだったかな…この部分は、英語のページで書いてあったのをWindowsAPI用にそのまんまなおしたって言う感じでした。

で、私の環境では少なくともYESですね。

ただ、↓こうやっても同じになりますし、感覚的にはやっぱGetWindowRectの方が良いと思いますので
現時点ではこう直しておくことにします。


宣言は

Void Setup( Dword index_to_select = 0, szt max_num_to_show = 0 ) const;


定義は

Void myComboBox::Setup( Dword i, const szt maxnum_ ) const {

SendMessage( wnd, WM_SETFONT, (WPARAM)FontLibrary::Default, False );

RECT rc;
GetWindowRect( wnd, &rc );

const szt n_ = ( maxnum_ ) ? maxnum_: num;

int cy = (int)SendMessage( wnd, CB_GETITEMHEIGHT, -1, 0 );
cy += (int)SendMessage( wnd, CB_GETITEMHEIGHT, 0, 0 ) * n_;
cy += GetSystemMetrics(SM_CYEDGE) * 4;

SetWindowPos( wnd, null, 0, 0, rc.right-rc.left, cy, SWP_NOMOVE|SWP_NOZORDER );

Select( i );

}



combo_measden->Setup(2,4); //選択インデックスと最大表示数を指定できる。

こいでどうでしょう?

お礼日時:2012/02/21 15:13

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています