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()
56 bool wxHeaderCtrl::Create(wxWindow
*parent
,
63 // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
64 // here as we already call InitCommonControls() in wxApp initialization
65 // code which covers this
67 if ( !CreateControl(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) )
70 if ( !MSWCreateControl(WC_HEADER
, _T(""), pos
, size
) )
76 WXDWORD
wxHeaderCtrl::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
78 WXDWORD msStyle
= wxControl::MSWGetStyle(style
, exstyle
);
80 if ( style
& wxHD_DRAGDROP
)
81 msStyle
|= HDS_DRAGDROP
;
83 // the control looks nicer with these styles and there doesn't seem to be
84 // any reason to not use them so we always do (as for HDS_HORZ it is 0
85 // anyhow but include it for clarity)
86 msStyle
|= HDS_HORZ
| HDS_BUTTONS
| HDS_FLAT
| HDS_FULLDRAG
| HDS_HOTTRACK
;
91 wxHeaderCtrl::~wxHeaderCtrl()
96 // ----------------------------------------------------------------------------
97 // wxHeaderCtrl scrolling
98 // ----------------------------------------------------------------------------
100 void wxHeaderCtrl::DoSetSize(int x
, int y
,
104 wxHeaderCtrlBase::DoSetSize(x
+ m_scrollOffset
, y
, w
- m_scrollOffset
, h
,
108 void wxHeaderCtrl::DoScrollHorz(int dx
)
110 // as the native control doesn't support offsetting its contents, we use a
111 // hack here to make it appear correctly when the parent is scrolled:
112 // instead of scrolling or repainting we simply move the control window
113 // itself: to be precise, offset it by the scroll increment to the left and
114 // increment its width to still extend to the right boundary to compensate
115 // for it (notice that dx is negative when scrolling to the right)
116 m_scrollOffset
+= dx
;
118 wxHeaderCtrlBase::DoSetSize(GetPosition().x
+ dx
, -1,
119 GetSize().x
- dx
, -1,
120 wxSIZE_USE_EXISTING
);
123 // ----------------------------------------------------------------------------
124 // wxHeaderCtrl geometry calculation
125 // ----------------------------------------------------------------------------
127 wxSize
wxHeaderCtrl::DoGetBestSize() const
129 RECT rc
= wxGetClientRect(GetHwndOf(GetParent()));
131 HDLAYOUT layout
= { &rc
, &wpos
};
132 if ( !Header_Layout(GetHwnd(), &layout
) )
134 wxLogLastError(_T("Header_Layout"));
135 return wxControl::DoGetBestSize();
138 return wxSize(wpos
.cx
, wpos
.cy
);
141 // ----------------------------------------------------------------------------
142 // wxHeaderCtrl columns managements
143 // ----------------------------------------------------------------------------
145 unsigned int wxHeaderCtrl::DoGetCount() const
147 return Header_GetItemCount(GetHwnd());
150 void wxHeaderCtrl::DoSetCount(unsigned int count
)
154 // first delete all old columns
155 const unsigned countOld
= DoGetCount();
156 for ( n
= 0; n
< countOld
; n
++ )
158 if ( !Header_DeleteItem(GetHwnd(), 0) )
160 wxLogLastError(_T("Header_DeleteItem"));
164 // and add the new ones
165 for ( n
= 0; n
< count
; n
++ )
167 DoInsertItem(n
, -1 /* default order, i.e. append */);
171 void wxHeaderCtrl::DoUpdate(unsigned int idx
)
173 // the native control does provide Header_SetItem() but it's inconvenient
174 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
175 // arrange not to block setting the width from there and the logic would be
176 // more complicated as we'd have to reset the old values as well as setting
177 // the new ones -- so instead just recreate the column
179 // we need to preserve the old position ourselves as the column doesn't
180 // store it (TODO: should it?)
181 const unsigned int pos
= GetColumnPos(idx
);
182 Header_DeleteItem(GetHwnd(), idx
);
183 DoInsertItem(idx
, pos
);
186 void wxHeaderCtrl::DoInsertItem(unsigned int idx
, int order
)
188 const wxHeaderColumn
& col
= GetColumn(idx
);
192 // notice that we need to store the string we use the pointer to until we
193 // pass it to the control
194 hdi
.mask
|= HDI_TEXT
;
195 wxWxCharBuffer buf
= col
.GetTitle().wx_str();
196 hdi
.pszText
= buf
.data();
197 hdi
.cchTextMax
= wxStrlen(buf
);
199 const wxBitmap bmp
= col
.GetBitmap();
202 hdi
.mask
|= HDI_IMAGE
;
206 const int bmpWidth
= bmp
.GetWidth(),
207 bmpHeight
= bmp
.GetHeight();
211 m_imageList
= new wxImageList(bmpWidth
, bmpHeight
);
212 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList
));
214 else // already have an image list
216 // check that all bitmaps we use have the same size
219 m_imageList
->GetSize(0, imageWidth
, imageHeight
);
221 wxASSERT_MSG( imageWidth
== bmpWidth
&& imageHeight
== bmpHeight
,
222 "all column bitmaps must have the same size" );
225 m_imageList
->Add(bmp
);
226 hdi
.iImage
= m_imageList
->GetImageCount() - 1;
228 else // no bitmap but we still need to update the item
230 hdi
.iImage
= I_IMAGENONE
;
234 if ( col
.GetAlignment() != wxALIGN_NOT
)
236 hdi
.mask
|= HDI_FORMAT
;
237 switch ( col
.GetAlignment() )
244 case wxALIGN_CENTER_HORIZONTAL
:
245 hdi
.fmt
|= HDF_CENTER
;
249 hdi
.fmt
|= HDF_RIGHT
;
253 wxFAIL_MSG( "invalid column header alignment" );
257 if ( col
.IsSortKey() )
259 hdi
.mask
|= HDI_FORMAT
;
260 hdi
.fmt
|= col
.IsSortOrderAscending() ? HDF_SORTUP
: HDF_SORTDOWN
;
263 if ( col
.GetWidth() != wxCOL_WIDTH_DEFAULT
|| col
.IsHidden() )
265 hdi
.mask
|= HDI_WIDTH
;
266 hdi
.cxy
= col
.IsHidden() ? 0 : col
.GetWidth();
271 hdi
.mask
|= HDI_ORDER
;
275 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM
, idx
, (LPARAM
)&hdi
) == -1 )
277 wxLogLastError(_T("Header_InsertItem()"));
281 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt
& order
)
283 if ( !Header_SetOrderArray(GetHwnd(), order
.size(), &order
[0]) )
285 wxLogLastError(_T("Header_GetOrderArray"));
289 wxArrayInt
wxHeaderCtrl::DoGetColumnsOrder() const
291 const unsigned count
= GetColumnCount();
292 wxArrayInt
order(count
);
293 if ( !Header_GetOrderArray(GetHwnd(), count
, &order
[0]) )
295 wxLogLastError(_T("Header_GetOrderArray"));
301 // ----------------------------------------------------------------------------
302 // wxHeaderCtrl events
303 // ----------------------------------------------------------------------------
305 wxEventType
wxHeaderCtrl::GetClickEventType(bool dblclk
, int button
)
311 evtType
= dblclk
? wxEVT_COMMAND_HEADER_DCLICK
312 : wxEVT_COMMAND_HEADER_CLICK
;
316 evtType
= dblclk
? wxEVT_COMMAND_HEADER_RIGHT_DCLICK
317 : wxEVT_COMMAND_HEADER_RIGHT_CLICK
;
321 evtType
= dblclk
? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK
322 : wxEVT_COMMAND_HEADER_MIDDLE_CLICK
;
326 wxFAIL_MSG( wxS("unexpected event type") );
327 evtType
= wxEVT_NULL
;
333 bool wxHeaderCtrl::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM
*result
)
335 NMHEADER
* const nmhdr
= (NMHEADER
*)lParam
;
337 wxEventType evtType
= wxEVT_NULL
;
338 int idx
= nmhdr
->iItem
;
342 const UINT code
= nmhdr
->hdr
.code
;
349 case HDN_ITEMDBLCLICK
:
350 evtType
= GetClickEventType(code
== HDN_ITEMDBLCLICK
, nmhdr
->iButton
);
353 // although we should get the notifications about the right clicks
354 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
355 // happen in practice on any Windows system up to 2003
360 idx
= wxMSWGetColumnClicked(&nmhdr
->hdr
, &pt
);
361 if ( idx
!= wxNOT_FOUND
)
362 evtType
= GetClickEventType(code
== NM_RDBLCLK
, 1);
363 //else: ignore clicks outside any column
367 case HDN_DIVIDERDBLCLICK
:
368 evtType
= wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK
;
372 // column resizing events
373 // ----------------------
375 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
376 // ASCII and Unicode versions of this message
377 case HDN_BEGINTRACKA
:
378 case HDN_BEGINTRACKW
:
379 // non-resizeable columns can't be resized no matter what, don't
380 // even generate any events for them
381 if ( !GetColumn(idx
).IsResizeable() )
387 evtType
= wxEVT_COMMAND_HEADER_BEGIN_RESIZE
;
392 width
= nmhdr
->pitem
->cxy
;
394 if ( evtType
== wxEVT_NULL
)
396 evtType
= wxEVT_COMMAND_HEADER_END_RESIZE
;
398 // don't generate events with invalid width
399 const int minWidth
= GetColumn(idx
).GetMinWidth();
400 if ( width
< minWidth
)
405 case HDN_ITEMCHANGING
:
406 if ( nmhdr
->pitem
&& (nmhdr
->pitem
->mask
& HDI_WIDTH
) )
408 // prevent the column from being shrunk beneath its min width
409 width
= nmhdr
->pitem
->cxy
;
410 if ( width
< GetColumn(idx
).GetMinWidth() )
412 // don't generate any events and prevent the change from
416 else // width is acceptable
418 // generate the resizing event from here as we don't seem
419 // to be getting HDN_TRACK events at all, at least with
421 evtType
= wxEVT_COMMAND_HEADER_RESIZING
;
427 // column reordering events
428 // ------------------------
431 // Windows sometimes sends us events with invalid column indices
435 // column must have the appropriate flag to be draggable
436 if ( !GetColumn(idx
).IsReorderable() )
442 evtType
= wxEVT_COMMAND_HEADER_BEGIN_REORDER
;
446 evtType
= wxEVT_COMMAND_HEADER_END_REORDER
;
448 wxASSERT_MSG( nmhdr
->pitem
->mask
& HDI_ORDER
, "should have order" );
449 order
= nmhdr
->pitem
->iOrder
;
452 case NM_RELEASEDCAPTURE
:
453 evtType
= wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED
;
458 // do generate the corresponding wx event
459 if ( evtType
!= wxEVT_NULL
)
461 wxHeaderCtrlEvent
event(evtType
, GetId());
462 event
.SetEventObject(this);
463 event
.SetColumn(idx
);
464 event
.SetWidth(width
);
466 event
.SetNewOrder(order
);
468 if ( GetEventHandler()->ProcessEvent(event
) )
470 if ( event
.IsAllowed() )
473 // we need to veto the default handling of this message, don't
474 // return to execute the code in the "if veto" branch below
481 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
482 // interpret TRUE return value as meaning to stop the control
483 // default handling of the message
489 return wxHeaderCtrlBase::MSWOnNotify(idCtrl
, lParam
, result
);
492 #endif // wxHAS_GENERIC_HEADERCTRL