コンボボックスの高さと幅を計算してみる

MFCでコンボボックスを使うときはフォームにコントロールを貼り付けてマウスでうにょ〜んって調整していたのですが、プログラムから動的に作成する場合それができないので、意味のあるウィンドウサイズを指定する必要があります。特に項目を追加したり削除したりといったことを頻繁におこなうときは結構面倒です。といっても所詮コンボボックスのウィンドウrectを指定するだけなんで何を指定するのかはある程度想像がつくのですが明示的に「こうしなさい」っていうドキュメントが見当たらなかったのでメモ。

必要なのは表示するテキストの矩形ですので、コンボボックス内で1番長い文字列を探してそのテキストメトリックを現在のコンテキストから取得します。仮にvectorcombo_stringsにコンボボックスに設定する文字列が格納されているとすれば、

vector::iterator max_len_elem = 
  max_element(combo_strings.begin(),combo_strings.end(),string_len_less());
SIZE w;
GetTextExtentPoint32(GetDC()->m_hDC,(*max_len_elem).c_str(),(*max_len_elem).size(),&w);

これでw.cxにコンボボックスの最大幅、w.cyに一行分の高さを取得できますので、コンボボックスの幅はw.cx、高さはw.cy*combo_strings.size();でいいはず。

RECT ctrl_rect;
ctrl_rect.top    = 30;
ctrl_rect.left   = 50;
ctrl_rect.right  = ctrl_rect.left + w.cx;
ctrl_rect.bottom = ctrl_rect.top + (w.cy*combo_strings.size());
m_combo.Create(CBS_DROPDOWNLIST|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP,ctrl_rect,this,NULL);

for(unsigned int i=0; i<combo_strings.size(); i++)
  m_combo.AddString(combo_strings[i].c_str());
m_combo.SetCurSel(0);

で、とりあえず5項目のコンボボックスで試してみるとなんか変。

最初

思惑としては矢印ボタンを押下したときスクロールバーなしで5項目すべて表示されると思ったのですが3行目までしか表示されません。また幅も項目の文字列が途中で途切れていて見苦しい。そこでウィンドウに3Dのボーダー幅があるのでその分を追加、さらに高さに関しては選択項目分の1行があるのでその分を追加してみます。

int border_w = GetSystemMetrics(SM_CXEDGE);
ctrl_rect.right  = ctrl_rect.left + w.cx + (border_w*2);
ctrl_rect.bottom = ctrl_rect.top + w.cy + (w.cy*combo_strings.size());

幅に関しては予想どおりほとんど変わらず1文字分くらい欠けてる。考えてみればドロップする矢印ボタンも幅の内なのでこの分も追加(ボタンに3Dボーダー幅があるのでこれも加算)。高さも行間分(この5項目なら行間*5)の高さを加算する必要がありそうなのでこれも追加。

int border_h = GetSystemMetrics(SM_CYEDGE);
int arrow_w  = GetSystemMetrics(SM_CXHSCROLL)+(border_w*2);
ctrl_rect.right  = ctrl_rect.left + w.cx + (border_w*2) + arrow_w ;
ctrl_rect.bottom = ctrl_rect.top + w.cy + ((w.cy+border_h)*combo_strings.size());

で、ようやくきれいに収まりました。

仮に項目の長さがバラバラでも1番長い項目にあわせているので大丈夫。この例は作成時のものですが、項目を動的に追加したり変更するような場合は、そのタイミングで矩形を計算してMoveWindowすれば都度都度最適なサイズに変更できそうですね。

正直、行間をSM_CYEDGEとしても良いか自信はありません。このあたりはプロパティで調整できればもっとビジネスロジックに集中できるのにね。まあ、細かいことやりたいときには仕方ないんでしょうが。とりあえず、全掲しときます。(Visual Studio 2010 sp1です)

struct string_len_less : 
  public std::binary_function<const std::string&,const std::string& , bool>
{
  bool operator()(const std::string& x,const std::string& y) const 
  { 
     return x.size() < y.size(); 
   };
};

void Ccombo_heightView::create_combobox(void)
{
  const char* combo_string [] = 
  {
     _T("1行目"),
     _T("2行目"),
     _T("3行目長い項目でも大丈夫"),
     _T("4行目"),
     _T("5行目")
   };

  vector<string> combo_strings;  
  for(int i=0; i<5; i++)
    combo_strings.push_back(combo_string[i]);

  vector<string>::iterator max_len_elem = 
    max_element(combo_strings.begin(),combo_strings.end(),string_len_less());

  int border_w = GetSystemMetrics(SM_CXEDGE);
  int border_h = GetSystemMetrics(SM_CYEDGE);
  int arrow_w  = GetSystemMetrics(SM_CXHSCROLL)+(border_w*2);

  SIZE w;
  GetTextExtentPoint32(
    GetDC()->m_hDC,
    (*max_len_elem).c_str(),
    (*max_len_elem).size(),&w);

  RECT ctrl_rect;
  ctrl_rect.top    = 30;
  ctrl_rect.left   = 50;
  ctrl_rect.right  = ctrl_rect.left + w.cx + (border_w*2) + arrow_w ;
  ctrl_rect.bottom = ctrl_rect.top + w.cy + ((w.cy+border_h)*combo_strings.size());
  m_combo.Create(
    CBS_DROPDOWNLIST|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP,ctrl_rect,this,NULL
   );
  
  for(unsigned int i=0; i<combo_strings.size(); i++)
    m_combo.AddString(combo_strings[i].c_str());
  
  m_combo.SetCurSel(0);

}

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/391

Comments