1 /////////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/msw/headerctrl.cpp 
   3 // Purpose:     implementation of wxHeaderCtrl for wxMSW 
   4 // Author:      Vadim Zeitlin 
   7 // Copyright:   (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org> 
   8 // Licence:     wxWindows licence 
   9 /////////////////////////////////////////////////////////////////////////////// 
  11 // ============================================================================ 
  13 // ============================================================================ 
  15 // ---------------------------------------------------------------------------- 
  17 // ---------------------------------------------------------------------------- 
  19 // for compilers that support precompilation, includes "wx.h". 
  20 #include "wx/wxprec.h" 
  33 #include "wx/headerctrl.h" 
  35 #ifndef wxHAS_GENERIC_HEADERCTRL 
  37 #include "wx/imaglist.h" 
  39 #include "wx/msw/wrapcctl.h" 
  40 #include "wx/msw/private.h" 
  42 #ifndef HDM_SETBITMAPMARGIN 
  43     #define HDM_SETBITMAPMARGIN 0x1234 
  46 #ifndef Header_SetBitmapMargin 
  47     #define Header_SetBitmapMargin(hwnd, margin) \ 
  48             ::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0) 
  51 // from src/msw/listctrl.cpp 
  52 extern int WXDLLIMPEXP_CORE 
wxMSWGetColumnClicked(NMHDR 
*nmhdr
, POINT 
*ptClick
); 
  54 // ============================================================================ 
  55 // wxHeaderCtrl implementation 
  56 // ============================================================================ 
  58 // ---------------------------------------------------------------------------- 
  59 // wxHeaderCtrl construction/destruction 
  60 // ---------------------------------------------------------------------------- 
  62 void wxHeaderCtrl::Init() 
  69 bool wxHeaderCtrl::Create(wxWindow 
*parent
, 
  76     // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES) 
  77     // here as we already call InitCommonControls() in wxApp initialization 
  78     // code which covers this 
  80     if ( !CreateControl(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) ) 
  83     if ( !MSWCreateControl(WC_HEADER
, _T(""), pos
, size
) ) 
  86     // special hack for margins when using comctl32.dll v6 or later: the 
  87     // default margin is too big and results in label truncation when the 
  88     // column width is just about right to show it together with the sort 
  89     // indicator, so reduce it to a smaller value (in principle we could even 
  90     // use 0 here but this starts to look ugly) 
  91     if ( wxApp::GetComCtl32Version() >= 600 ) 
  93         Header_SetBitmapMargin(GetHwnd(), ::GetSystemMetrics(SM_CXEDGE
)); 
  99 WXDWORD 
wxHeaderCtrl::MSWGetStyle(long style
, WXDWORD 
*exstyle
) const 
 101     WXDWORD msStyle 
= wxControl::MSWGetStyle(style
, exstyle
); 
 103     if ( style 
& wxHD_ALLOW_REORDER 
) 
 104         msStyle 
|= HDS_DRAGDROP
; 
 106     // the control looks nicer with these styles and there doesn't seem to be 
 107     // any reason to not use them so we always do (as for HDS_HORZ it is 0 
 108     // anyhow but include it for clarity) 
 109     msStyle 
|= HDS_HORZ 
| HDS_BUTTONS 
| HDS_FLAT 
| HDS_FULLDRAG 
| HDS_HOTTRACK
; 
 114 wxHeaderCtrl::~wxHeaderCtrl() 
 119 // ---------------------------------------------------------------------------- 
 120 // wxHeaderCtrl scrolling 
 121 // ---------------------------------------------------------------------------- 
 123 void wxHeaderCtrl::DoSetSize(int x
, int y
, 
 127     wxHeaderCtrlBase::DoSetSize(x 
+ m_scrollOffset
, y
, w 
- m_scrollOffset
, h
, 
 131 void wxHeaderCtrl::DoScrollHorz(int dx
) 
 133     // as the native control doesn't support offsetting its contents, we use a 
 134     // hack here to make it appear correctly when the parent is scrolled: 
 135     // instead of scrolling or repainting we simply move the control window 
 136     // itself: to be precise, offset it by the scroll increment to the left and 
 137     // increment its width to still extend to the right boundary to compensate 
 138     // for it (notice that dx is negative when scrolling to the right) 
 139     m_scrollOffset 
+= dx
; 
 141     wxHeaderCtrlBase::DoSetSize(GetPosition().x 
+ dx
, -1, 
 142                                 GetSize().x 
- dx
, -1, 
 143                                 wxSIZE_USE_EXISTING
); 
 146 // ---------------------------------------------------------------------------- 
 147 // wxHeaderCtrl geometry calculation 
 148 // ---------------------------------------------------------------------------- 
 150 wxSize 
wxHeaderCtrl::DoGetBestSize() const 
 152     RECT rc 
= wxGetClientRect(GetHwndOf(GetParent())); 
 154     HDLAYOUT layout 
= { &rc
, &wpos 
}; 
 155     if ( !Header_Layout(GetHwnd(), &layout
) ) 
 157         wxLogLastError(_T("Header_Layout")); 
 158         return wxControl::DoGetBestSize(); 
 161     return wxSize(wpos
.cx
, wpos
.cy
); 
 164 // ---------------------------------------------------------------------------- 
 165 // wxHeaderCtrl columns managements 
 166 // ---------------------------------------------------------------------------- 
 168 unsigned int wxHeaderCtrl::DoGetCount() const 
 170     // we can't use Header_GetItemCount() here because it doesn't take the 
 171     // hidden columns into account and we can't find the hidden columns after 
 172     // the last shown one in MSWFromNativeIdx() without knowing where to stop 
 173     // so we have to store the columns count internally 
 177 int wxHeaderCtrl::GetShownColumnsCount() const 
 179     const int numItems 
= Header_GetItemCount(GetHwnd()); 
 181     wxASSERT_MSG( numItems 
>= 0 && (unsigned)numItems 
<= m_numColumns
, 
 182                   "unexpected number of items in the native control" ); 
 187 void wxHeaderCtrl::DoSetCount(unsigned int count
) 
 191     // first delete all old columns 
 192     const unsigned countOld 
= GetShownColumnsCount(); 
 193     for ( n 
= 0; n 
< countOld
; n
++ ) 
 195         if ( !Header_DeleteItem(GetHwnd(), 0) ) 
 197             wxLogLastError(_T("Header_DeleteItem")); 
 201     // update the column indices order array before changing m_numColumns 
 202     DoResizeColumnIndices(m_colIndices
, count
); 
 204     // and add the new ones 
 205     m_numColumns 
= count
; 
 206     m_isHidden
.resize(m_numColumns
); 
 207     for ( n 
= 0; n 
< count
; n
++ ) 
 209         const wxHeaderColumn
& col 
= GetColumn(n
); 
 212             m_isHidden
[n
] = false; 
 214             DoInsertItem(col
, n
); 
 216         else // hidden initially 
 218             m_isHidden
[n
] = true; 
 223 void wxHeaderCtrl::DoUpdate(unsigned int idx
) 
 225     // the native control does provide Header_SetItem() but it's inconvenient 
 226     // to use it because it sends HDN_ITEMCHANGING messages and we'd have to 
 227     // arrange not to block setting the width from there and the logic would be 
 228     // more complicated as we'd have to reset the old values as well as setting 
 229     // the new ones -- so instead just recreate the column 
 231     const wxHeaderColumn
& col 
= GetColumn(idx
); 
 232     if ( col
.IsHidden() ) 
 234         // column is hidden now 
 235         if ( !m_isHidden
[idx
] ) 
 237             // but it wasn't hidden before, so remove it 
 238             Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
)); 
 240             m_isHidden
[idx
] = true; 
 242         //else: nothing to do, updating hidden column doesn't have any effect 
 244     else // column is shown now 
 246         if ( m_isHidden
[idx
] ) 
 248             m_isHidden
[idx
] = false; 
 250         else // and it was shown before as well 
 252             // we need to remove the old column 
 253             Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
)); 
 256         DoInsertItem(col
, idx
); 
 260 void wxHeaderCtrl::DoInsertItem(const wxHeaderColumn
& col
, unsigned int idx
) 
 262     wxASSERT_MSG( !col
.IsHidden(), "should only be called for shown columns" ); 
 266     // notice that we need to store the string we use the pointer to until we 
 267     // pass it to the control 
 268     hdi
.mask 
|= HDI_TEXT
; 
 269     wxWxCharBuffer buf 
= col
.GetTitle().wx_str(); 
 270     hdi
.pszText 
= buf
.data(); 
 271     hdi
.cchTextMax 
= wxStrlen(buf
); 
 273     const wxBitmap bmp 
= col
.GetBitmap(); 
 276         hdi
.mask 
|= HDI_IMAGE
; 
 280             const int bmpWidth 
= bmp
.GetWidth(), 
 281                       bmpHeight 
= bmp
.GetHeight(); 
 285                 m_imageList 
= new wxImageList(bmpWidth
, bmpHeight
); 
 286                 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList
)); 
 288             else // already have an image list 
 290                 // check that all bitmaps we use have the same size 
 293                 m_imageList
->GetSize(0, imageWidth
, imageHeight
); 
 295                 wxASSERT_MSG( imageWidth 
== bmpWidth 
&& imageHeight 
== bmpHeight
, 
 296                               "all column bitmaps must have the same size" ); 
 299             m_imageList
->Add(bmp
); 
 300             hdi
.iImage 
= m_imageList
->GetImageCount() - 1; 
 302         else // no bitmap but we still need to update the item 
 304             hdi
.iImage 
= I_IMAGENONE
; 
 308     if ( col
.GetAlignment() != wxALIGN_NOT 
) 
 310         hdi
.mask 
|= HDI_FORMAT 
| HDF_LEFT
; 
 311         switch ( col
.GetAlignment() ) 
 318             case wxALIGN_CENTER_HORIZONTAL
: 
 319                 hdi
.fmt 
|= HDF_CENTER
; 
 323                 hdi
.fmt 
|= HDF_RIGHT
; 
 327                 wxFAIL_MSG( "invalid column header alignment" ); 
 331     if ( col
.IsSortKey() ) 
 333         hdi
.mask 
|= HDI_FORMAT
; 
 334         hdi
.fmt 
|= col
.IsSortOrderAscending() ? HDF_SORTUP 
: HDF_SORTDOWN
; 
 337     if ( col
.GetWidth() != wxCOL_WIDTH_DEFAULT 
) 
 339         hdi
.mask 
|= HDI_WIDTH
; 
 340         hdi
.cxy 
= col
.GetWidth(); 
 343     hdi
.mask 
|= HDI_ORDER
; 
 344     hdi
.iOrder 
= MSWToNativeOrder(m_colIndices
.Index(idx
)); 
 346     if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM
, 
 347                        MSWToNativeIdx(idx
), (LPARAM
)&hdi
) == -1 ) 
 349         wxLogLastError(_T("Header_InsertItem()")); 
 353 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt
& order
) 
 355     wxArrayInt orderShown
; 
 356     orderShown
.reserve(m_numColumns
); 
 358     for ( unsigned n 
= 0; n 
< m_numColumns
; n
++ ) 
 360         const int idx 
= order
[n
]; 
 361         if ( GetColumn(idx
).IsShown() ) 
 362             orderShown
.push_back(MSWToNativeIdx(idx
)); 
 365     if ( !Header_SetOrderArray(GetHwnd(), orderShown
.size(), &orderShown
[0]) ) 
 367         wxLogLastError(_T("Header_GetOrderArray")); 
 370     m_colIndices 
= order
; 
 373 wxArrayInt 
wxHeaderCtrl::DoGetColumnsOrder() const 
 375     // we don't use Header_GetOrderArray() here because it doesn't return 
 376     // information about the hidden columns, instead we just save the columns 
 377     // order array in DoSetColumnsOrder() and update it when they're reordered 
 381 // ---------------------------------------------------------------------------- 
 382 // wxHeaderCtrl indexes and positions translation 
 383 // ---------------------------------------------------------------------------- 
 385 int wxHeaderCtrl::MSWToNativeIdx(int idx
) 
 387     // don't check for GetColumn(idx).IsShown() as it could have just became 
 388     // false and we may be called from DoUpdate() to delete the old column 
 389     wxASSERT_MSG( !m_isHidden
[idx
], 
 390                   "column must be visible to have an " 
 391                   "index in the native control" ); 
 394     for ( int i 
= 0; i 
< idx
; i
++ ) 
 396         if ( GetColumn(i
).IsHidden() ) 
 397             item
--; // one less column the native control knows about 
 400     wxASSERT_MSG( item 
>= 0 && item 
<= GetShownColumnsCount(), "logic error" ); 
 405 int wxHeaderCtrl::MSWFromNativeIdx(int item
) 
 407     wxASSERT_MSG( item 
>= 0 && item 
< GetShownColumnsCount(), 
 408                   "column index out of range" ); 
 410     // reverse the above function 
 413     for ( unsigned n 
= 0; n 
< m_numColumns
; n
++ ) 
 418         if ( GetColumn(n
).IsHidden() ) 
 422     wxASSERT_MSG( MSWToNativeIdx(idx
) == item
, "logic error" ); 
 427 int wxHeaderCtrl::MSWToNativeOrder(int pos
) 
 429     wxASSERT_MSG( pos 
>= 0 && static_cast<unsigned>(pos
) < m_numColumns
, 
 430                   "column position out of range" ); 
 433     for ( int n 
= 0; n 
< pos
; n
++ ) 
 435         if ( GetColumn(m_colIndices
[n
]).IsHidden() ) 
 439     wxASSERT_MSG( order 
>= 0 && order 
<= GetShownColumnsCount(), "logic error" ); 
 444 int wxHeaderCtrl::MSWFromNativeOrder(int order
) 
 446     wxASSERT_MSG( order 
>= 0 && order 
< GetShownColumnsCount(), 
 447                   "native column position out of range" ); 
 449     unsigned pos 
= order
; 
 450     for ( unsigned n 
= 0; n 
< m_numColumns
; n
++ ) 
 455         if ( GetColumn(m_colIndices
[n
]).IsHidden() ) 
 459     wxASSERT_MSG( MSWToNativeOrder(pos
) == order
, "logic error" ); 
 464 // ---------------------------------------------------------------------------- 
 465 // wxHeaderCtrl events 
 466 // ---------------------------------------------------------------------------- 
 468 wxEventType 
wxHeaderCtrl::GetClickEventType(bool dblclk
, int button
) 
 474             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_DCLICK
 
 475                              : wxEVT_COMMAND_HEADER_CLICK
; 
 479             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_RIGHT_DCLICK
 
 480                              : wxEVT_COMMAND_HEADER_RIGHT_CLICK
; 
 484             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK
 
 485                              : wxEVT_COMMAND_HEADER_MIDDLE_CLICK
; 
 489             wxFAIL_MSG( wxS("unexpected event type") ); 
 490             evtType 
= wxEVT_NULL
; 
 496 bool wxHeaderCtrl::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM 
*result
) 
 498     NMHEADER 
* const nmhdr 
= (NMHEADER 
*)lParam
; 
 500     wxEventType evtType 
= wxEVT_NULL
; 
 504     const UINT code 
= nmhdr
->hdr
.code
; 
 506     // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE 
 507     // so only access for header control events (and yes, the direction of 
 508     // comparisons with FIRST/LAST is correct even if it seems inverted) 
 509     int idx 
= code 
<= HDN_FIRST 
&& code 
> HDN_LAST 
? nmhdr
->iItem 
: -1; 
 512         // we also get bogus HDN_BEGINDRAG with -1 index so don't call 
 513         // MSWFromNativeIdx() unconditionally for nmhdr->iItem 
 514         idx 
= MSWFromNativeIdx(idx
); 
 523         case HDN_ITEMDBLCLICK
: 
 524             evtType 
= GetClickEventType(code 
== HDN_ITEMDBLCLICK
, nmhdr
->iButton
); 
 527             // although we should get the notifications about the right clicks 
 528             // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't 
 529             // happen in practice on any Windows system up to 2003 
 534                 idx 
= wxMSWGetColumnClicked(&nmhdr
->hdr
, &pt
); 
 535                 if ( idx 
!= wxNOT_FOUND 
) 
 537                     idx 
= MSWFromNativeIdx(idx
); 
 538                     evtType 
= GetClickEventType(code 
== NM_RDBLCLK
, 1); 
 540                 //else: ignore clicks outside any column 
 544         case HDN_DIVIDERDBLCLICK
: 
 545             evtType 
= wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK
; 
 549         // column resizing events 
 550         // ---------------------- 
 552         // see comments in wxListCtrl::MSWOnNotify() for why we catch both 
 553         // ASCII and Unicode versions of this message 
 554         case HDN_BEGINTRACKA
: 
 555         case HDN_BEGINTRACKW
: 
 556             // non-resizeable columns can't be resized no matter what, don't 
 557             // even generate any events for them 
 558             if ( !GetColumn(idx
).IsResizeable() ) 
 564             evtType 
= wxEVT_COMMAND_HEADER_BEGIN_RESIZE
; 
 569             width 
= nmhdr
->pitem
->cxy
; 
 571             if ( evtType 
== wxEVT_NULL 
) 
 573                 evtType 
= wxEVT_COMMAND_HEADER_END_RESIZE
; 
 575                 // don't generate events with invalid width 
 576                 const int minWidth 
= GetColumn(idx
).GetMinWidth(); 
 577                 if ( width 
< minWidth 
) 
 582         case HDN_ITEMCHANGING
: 
 583             if ( nmhdr
->pitem 
&& (nmhdr
->pitem
->mask 
& HDI_WIDTH
) ) 
 585                 // prevent the column from being shrunk beneath its min width 
 586                 width 
= nmhdr
->pitem
->cxy
; 
 587                 if ( width 
< GetColumn(idx
).GetMinWidth() ) 
 589                     // don't generate any events and prevent the change from 
 593                 else // width is acceptable 
 595                     // generate the resizing event from here as we don't seem 
 596                     // to be getting HDN_TRACK events at all, at least with 
 598                     evtType 
= wxEVT_COMMAND_HEADER_RESIZING
; 
 604         // column reordering events 
 605         // ------------------------ 
 608             // Windows sometimes sends us events with invalid column indices 
 609             if ( nmhdr
->iItem 
== -1 ) 
 612             // column must have the appropriate flag to be draggable 
 613             if ( !GetColumn(idx
).IsReorderable() ) 
 619             evtType 
= wxEVT_COMMAND_HEADER_BEGIN_REORDER
; 
 623             wxASSERT_MSG( nmhdr
->pitem
->mask 
& HDI_ORDER
, "should have order" ); 
 624             order 
= nmhdr
->pitem
->iOrder
; 
 626             // we also get messages with invalid order when column reordering 
 627             // is cancelled (e.g. by pressing Esc) 
 631             order 
= MSWFromNativeOrder(order
); 
 633             evtType 
= wxEVT_COMMAND_HEADER_END_REORDER
; 
 636         case NM_RELEASEDCAPTURE
: 
 637             evtType 
= wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED
; 
 642     // do generate the corresponding wx event 
 643     if ( evtType 
!= wxEVT_NULL 
) 
 645         wxHeaderCtrlEvent 
event(evtType
, GetId()); 
 646         event
.SetEventObject(this); 
 647         event
.SetColumn(idx
); 
 648         event
.SetWidth(width
); 
 650             event
.SetNewOrder(order
); 
 652         if ( GetEventHandler()->ProcessEvent(event
) ) 
 654             if ( event
.IsAllowed() ) 
 655                 return true;    // skip default message handling below 
 657             // we need to veto the default handling of this message, don't 
 658             // return to execute the code in the "if veto" branch below 
 661         else // not processed 
 663             // special post-processing for HDN_ENDDRAG: we need to update the 
 664             // internal column indices array if this is allowed to go ahead as 
 665             // the native control is going to reorder its columns now 
 666             if ( evtType 
== wxEVT_COMMAND_HEADER_END_REORDER 
) 
 667                 MoveColumnInOrderArray(m_colIndices
, idx
, order
); 
 673         // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING 
 674         // interpret TRUE return value as meaning to stop the control 
 675         // default handling of the message 
 681     return wxHeaderCtrlBase::MSWOnNotify(idCtrl
, lParam
, result
); 
 684 #endif // wxHAS_GENERIC_HEADERCTRL 
 686 #endif // wxUSE_HEADERCTRL