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" 
  30 #include "wx/headerctrl.h" 
  32 #ifndef wxHAS_GENERIC_HEADERCTRL 
  34 #include "wx/imaglist.h" 
  36 #include "wx/msw/wrapcctl.h" 
  37 #include "wx/msw/private.h" 
  39 // from src/msw/listctrl.cpp 
  40 extern int WXDLLIMPEXP_CORE 
wxMSWGetColumnClicked(NMHDR 
*nmhdr
, POINT 
*ptClick
); 
  42 // ============================================================================ 
  43 // wxHeaderCtrl implementation 
  44 // ============================================================================ 
  46 // ---------------------------------------------------------------------------- 
  47 // wxHeaderCtrl construction/destruction 
  48 // ---------------------------------------------------------------------------- 
  50 void wxHeaderCtrl::Init() 
  55 bool wxHeaderCtrl::Create(wxWindow 
*parent
, 
  62     // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES) 
  63     // here as we already call InitCommonControls() in wxApp initialization 
  64     // code which covers this 
  66     if ( !CreateControl(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) ) 
  69     if ( !MSWCreateControl(WC_HEADER
, _T(""), pos
, size
) ) 
  75 WXDWORD 
wxHeaderCtrl::MSWGetStyle(long style
, WXDWORD 
*exstyle
) const 
  77     WXDWORD msStyle 
= wxControl::MSWGetStyle(style
, exstyle
); 
  79     if ( style 
& wxHD_DRAGDROP 
) 
  80         msStyle 
|= HDS_DRAGDROP
; 
  82     // the control looks nicer with these styles and there doesn't seem to be 
  83     // any reason to not use them so we always do (as for HDS_HORZ it is 0 
  84     // anyhow but include it for clarity) 
  85     msStyle 
|= HDS_HORZ 
| HDS_BUTTONS 
| HDS_FLAT 
| HDS_FULLDRAG 
| HDS_HOTTRACK
; 
  90 wxHeaderCtrl::~wxHeaderCtrl() 
  95 // ---------------------------------------------------------------------------- 
  96 // wxHeaderCtrl scrolling 
  97 // ---------------------------------------------------------------------------- 
  99 void wxHeaderCtrl::DoScrollHorz(int dx
) 
 101     // as the native control doesn't support offsetting its contents, we use a 
 102     // hack here to make it appear correctly when the parent is scrolled: 
 103     // instead of scrolling or repainting we simply move the control window 
 104     // itself: to be precise, offset it by the scroll increment to the left and 
 105     // increment its width to still extend to the right boundary to compensate 
 106     // for it (notice that dx is negative when scrolling to the right) 
 107     SetSize(GetPosition().x 
+ dx
, -1, GetSize().x 
- dx
, -1, wxSIZE_USE_EXISTING
); 
 110 // ---------------------------------------------------------------------------- 
 111 // wxHeaderCtrl geometry calculation 
 112 // ---------------------------------------------------------------------------- 
 114 wxSize 
wxHeaderCtrl::DoGetBestSize() const 
 116     RECT rc 
= wxGetClientRect(GetHwndOf(GetParent())); 
 118     HDLAYOUT layout 
= { &rc
, &wpos 
}; 
 119     if ( !Header_Layout(GetHwnd(), &layout
) ) 
 121         wxLogLastError(_T("Header_Layout")); 
 122         return wxControl::DoGetBestSize(); 
 125     return wxSize(wpos
.cx
, wpos
.cy
); 
 128 // ---------------------------------------------------------------------------- 
 129 // wxHeaderCtrl columns managements 
 130 // ---------------------------------------------------------------------------- 
 132 unsigned int wxHeaderCtrl::DoGetCount() const 
 134     return Header_GetItemCount(GetHwnd()); 
 137 void wxHeaderCtrl::DoSetCount(unsigned int count
) 
 141     // first delete all old columns 
 142     const unsigned countOld 
= DoGetCount(); 
 143     for ( n 
= 0; n 
< countOld
; n
++ ) 
 145         if ( !Header_DeleteItem(GetHwnd(), 0) ) 
 147             wxLogLastError(_T("Header_DeleteItem")); 
 151     // and add the new ones 
 152     for ( n 
= 0; n 
< count
; n
++ ) 
 154         DoInsertItem(n
, -1 /* default order, i.e. append */); 
 158 void wxHeaderCtrl::DoUpdate(unsigned int idx
) 
 160     // the native control does provide Header_SetItem() but it's inconvenient 
 161     // to use it because it sends HDN_ITEMCHANGING messages and we'd have to 
 162     // arrange not to block setting the width from there and the logic would be 
 163     // more complicated as we'd have to reset the old values as well as setting 
 164     // the new ones -- so instead just recreate the column 
 166     // we need to preserve the old position ourselves as the column doesn't 
 167     // store it (TODO: should it?) 
 168     const unsigned int pos 
= GetColumnPos(idx
); 
 169     Header_DeleteItem(GetHwnd(), idx
); 
 170     DoInsertItem(idx
, pos
); 
 173 void wxHeaderCtrl::DoInsertItem(unsigned int idx
, int order
) 
 175     const wxHeaderColumn
& col 
= GetColumn(idx
); 
 179     // notice that we need to store the string we use the pointer to until we 
 180     // pass it to the control 
 181     hdi
.mask 
|= HDI_TEXT
; 
 182     wxWxCharBuffer buf 
= col
.GetTitle().wx_str(); 
 183     hdi
.pszText 
= buf
.data(); 
 184     hdi
.cchTextMax 
= wxStrlen(buf
); 
 186     const wxBitmap bmp 
= col
.GetBitmap(); 
 189         hdi
.mask 
|= HDI_IMAGE
; 
 193             const int bmpWidth 
= bmp
.GetWidth(), 
 194                       bmpHeight 
= bmp
.GetHeight(); 
 198                 m_imageList 
= new wxImageList(bmpWidth
, bmpHeight
); 
 199                 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList
)); 
 201             else // already have an image list 
 203                 // check that all bitmaps we use have the same size 
 206                 m_imageList
->GetSize(0, imageWidth
, imageHeight
); 
 208                 wxASSERT_MSG( imageWidth 
== bmpWidth 
&& imageHeight 
== bmpHeight
, 
 209                               "all column bitmaps must have the same size" ); 
 212             m_imageList
->Add(bmp
); 
 213             hdi
.iImage 
= m_imageList
->GetImageCount() - 1; 
 215         else // no bitmap but we still need to update the item 
 217             hdi
.iImage 
= I_IMAGENONE
; 
 221     if ( col
.GetAlignment() != wxALIGN_NOT 
) 
 223         hdi
.mask 
|= HDI_FORMAT
; 
 224         switch ( col
.GetAlignment() ) 
 231             case wxALIGN_CENTER_HORIZONTAL
: 
 232                 hdi
.fmt 
|= HDF_CENTER
; 
 236                 hdi
.fmt 
|= HDF_RIGHT
; 
 240                 wxFAIL_MSG( "invalid column header alignment" ); 
 244     if ( col
.IsSortKey() ) 
 246         hdi
.mask 
|= HDI_FORMAT
; 
 247         hdi
.fmt 
|= col
.IsSortOrderAscending() ? HDF_SORTUP 
: HDF_SORTDOWN
; 
 250     if ( col
.GetWidth() != wxCOL_WIDTH_DEFAULT 
|| col
.IsHidden() ) 
 252         hdi
.mask 
|= HDI_WIDTH
; 
 253         hdi
.cxy 
= col
.IsHidden() ? 0 : col
.GetWidth(); 
 258         hdi
.mask 
|= HDI_ORDER
; 
 262     if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM
, idx
, (LPARAM
)&hdi
) == -1 ) 
 264         wxLogLastError(_T("Header_InsertItem()")); 
 268 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt
& order
) 
 270     if ( !Header_SetOrderArray(GetHwnd(), order
.size(), &order
[0]) ) 
 272         wxLogLastError(_T("Header_GetOrderArray")); 
 276 wxArrayInt 
wxHeaderCtrl::DoGetColumnsOrder() const 
 278     const unsigned count 
= GetColumnCount(); 
 279     wxArrayInt 
order(count
); 
 280     if ( !Header_GetOrderArray(GetHwnd(), count
, &order
[0]) ) 
 282         wxLogLastError(_T("Header_GetOrderArray")); 
 288 // ---------------------------------------------------------------------------- 
 289 // wxHeaderCtrl events 
 290 // ---------------------------------------------------------------------------- 
 292 wxEventType 
wxHeaderCtrl::GetClickEventType(bool dblclk
, int button
) 
 298             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_DCLICK
 
 299                              : wxEVT_COMMAND_HEADER_CLICK
; 
 303             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_RIGHT_DCLICK
 
 304                              : wxEVT_COMMAND_HEADER_RIGHT_CLICK
; 
 308             evtType 
= dblclk 
? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK
 
 309                              : wxEVT_COMMAND_HEADER_MIDDLE_CLICK
; 
 313             wxFAIL_MSG( wxS("unexpected event type") ); 
 314             evtType 
= wxEVT_NULL
; 
 320 bool wxHeaderCtrl::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM 
*result
) 
 322     NMHEADER 
* const nmhdr 
= (NMHEADER 
*)lParam
; 
 324     wxEventType evtType 
= wxEVT_NULL
; 
 325     int idx 
= nmhdr
->iItem
; 
 329     const UINT code 
= nmhdr
->hdr
.code
; 
 336         case HDN_ITEMDBLCLICK
: 
 337             evtType 
= GetClickEventType(code 
== HDN_ITEMDBLCLICK
, nmhdr
->iButton
); 
 340             // although we should get the notifications about the right clicks 
 341             // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't 
 342             // happen in practice on any Windows system up to 2003 
 347                 idx 
= wxMSWGetColumnClicked(&nmhdr
->hdr
, &pt
); 
 348                 if ( idx 
!= wxNOT_FOUND 
) 
 349                     evtType 
= GetClickEventType(code 
== NM_RDBLCLK
, 1); 
 350                 //else: ignore clicks outside any column 
 354         case HDN_DIVIDERDBLCLICK
: 
 355             evtType 
= wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK
; 
 359         // column resizing events 
 360         // ---------------------- 
 362         // see comments in wxListCtrl::MSWOnNotify() for why we catch both 
 363         // ASCII and Unicode versions of this message 
 364         case HDN_BEGINTRACKA
: 
 365         case HDN_BEGINTRACKW
: 
 366             // non-resizeable columns can't be resized no matter what, don't 
 367             // even generate any events for them 
 368             if ( !GetColumn(idx
).IsResizeable() ) 
 374             evtType 
= wxEVT_COMMAND_HEADER_BEGIN_RESIZE
; 
 379             if ( evtType 
== wxEVT_NULL 
) 
 380                 evtType 
= wxEVT_COMMAND_HEADER_RESIZING
; 
 385             width 
= nmhdr
->pitem
->cxy
; 
 387             if ( evtType 
== wxEVT_NULL 
) 
 389                 evtType 
= wxEVT_COMMAND_HEADER_END_RESIZE
; 
 391                 // don't generate events with invalid width 
 392                 const int minWidth 
= GetColumn(idx
).GetMinWidth(); 
 393                 if ( width 
< minWidth 
) 
 398         case HDN_ITEMCHANGING
: 
 399             if ( nmhdr
->pitem 
&& (nmhdr
->pitem
->mask 
& HDI_WIDTH
) ) 
 401                 // prevent the column from being shrunk beneath its min width 
 402                 if ( nmhdr
->pitem
->cxy 
< GetColumn(idx
).GetMinWidth() ) 
 408         // column reordering events 
 409         // ------------------------ 
 412             // Windows sometimes sends us events with invalid column indices 
 416             // column must have the appropriate flag to be draggable 
 417             if ( !GetColumn(idx
).IsReorderable() ) 
 423             evtType 
= wxEVT_COMMAND_HEADER_BEGIN_REORDER
; 
 427             evtType 
= wxEVT_COMMAND_HEADER_END_REORDER
; 
 429             wxASSERT_MSG( nmhdr
->pitem
->mask 
& HDI_ORDER
, "should have order" ); 
 430             order 
= nmhdr
->pitem
->iOrder
; 
 433         case NM_RELEASEDCAPTURE
: 
 434             evtType 
= wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED
; 
 439     // do generate the corresponding wx event 
 440     if ( evtType 
!= wxEVT_NULL 
) 
 442         wxHeaderCtrlEvent 
event(evtType
, GetId()); 
 443         event
.SetEventObject(this); 
 444         event
.SetColumn(idx
); 
 445         event
.SetWidth(width
); 
 447             event
.SetNewOrder(order
); 
 449         if ( GetEventHandler()->ProcessEvent(event
) ) 
 451             if ( event
.IsAllowed() ) 
 454             // we need to veto the default handling of this message, don't 
 455             // return to execute the code in the "if veto" branch below 
 462         // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING 
 463         // interpret TRUE return value as meaning to stop the control 
 464         // default handling of the message 
 470     return wxHeaderCtrlBase::MSWOnNotify(idCtrl
, lParam
, result
); 
 473 #endif // wxHAS_GENERIC_HEADERCTRL