1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/headerctrl.cpp
3 // Purpose: implementation of wxHeaderCtrl for wxMSW
4 // Author: Vadim Zeitlin
6 // Copyright: (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
10 // ============================================================================
12 // ============================================================================
14 // ----------------------------------------------------------------------------
16 // ----------------------------------------------------------------------------
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
32 #include "wx/headerctrl.h"
34 #ifndef wxHAS_GENERIC_HEADERCTRL
36 #include "wx/imaglist.h"
38 #include "wx/msw/wrapcctl.h"
39 #include "wx/msw/private.h"
41 #ifndef HDM_SETBITMAPMARGIN
42 #define HDM_SETBITMAPMARGIN 0x1234
45 #ifndef Header_SetBitmapMargin
46 #define Header_SetBitmapMargin(hwnd, margin) \
47 ::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0)
50 // from src/msw/listctrl.cpp
51 extern int WXDLLIMPEXP_CORE
wxMSWGetColumnClicked(NMHDR
*nmhdr
, POINT
*ptClick
);
53 // ============================================================================
54 // wxHeaderCtrl implementation
55 // ============================================================================
57 // ----------------------------------------------------------------------------
58 // wxHeaderCtrl construction/destruction
59 // ----------------------------------------------------------------------------
61 void wxHeaderCtrl::Init()
66 m_colBeingDragged
= -1;
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
, wxT(""), 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 // NOTE: don't use however HDS_FLAT because it makes the control look
110 // non-native when running WinXP in classic mode
111 msStyle
|= HDS_HORZ
| HDS_BUTTONS
| HDS_FULLDRAG
| HDS_HOTTRACK
;
116 wxHeaderCtrl::~wxHeaderCtrl()
121 // ----------------------------------------------------------------------------
122 // wxHeaderCtrl scrolling
123 // ----------------------------------------------------------------------------
125 void wxHeaderCtrl::DoSetSize(int x
, int y
,
129 wxHeaderCtrlBase::DoSetSize(x
+ m_scrollOffset
, y
, w
- m_scrollOffset
, h
,
133 void wxHeaderCtrl::DoScrollHorz(int dx
)
135 // as the native control doesn't support offsetting its contents, we use a
136 // hack here to make it appear correctly when the parent is scrolled:
137 // instead of scrolling or repainting we simply move the control window
138 // itself: to be precise, offset it by the scroll increment to the left and
139 // increment its width to still extend to the right boundary to compensate
140 // for it (notice that dx is negative when scrolling to the right)
141 m_scrollOffset
+= dx
;
143 wxHeaderCtrlBase::DoSetSize(GetPosition().x
+ dx
, -1,
144 GetSize().x
- dx
, -1,
145 wxSIZE_USE_EXISTING
);
148 // ----------------------------------------------------------------------------
149 // wxHeaderCtrl geometry calculation
150 // ----------------------------------------------------------------------------
152 wxSize
wxHeaderCtrl::DoGetBestSize() const
154 RECT rc
= wxGetClientRect(GetHwndOf(GetParent()));
156 HDLAYOUT layout
= { &rc
, &wpos
};
157 if ( !Header_Layout(GetHwnd(), &layout
) )
159 wxLogLastError(wxT("Header_Layout"));
160 return wxControl::DoGetBestSize();
163 return wxSize(wpos
.cx
, wpos
.cy
);
166 // ----------------------------------------------------------------------------
167 // wxHeaderCtrl columns managements
168 // ----------------------------------------------------------------------------
170 unsigned int wxHeaderCtrl::DoGetCount() const
172 // we can't use Header_GetItemCount() here because it doesn't take the
173 // hidden columns into account and we can't find the hidden columns after
174 // the last shown one in MSWFromNativeIdx() without knowing where to stop
175 // so we have to store the columns count internally
179 int wxHeaderCtrl::GetShownColumnsCount() const
181 const int numItems
= Header_GetItemCount(GetHwnd());
183 wxASSERT_MSG( numItems
>= 0 && (unsigned)numItems
<= m_numColumns
,
184 "unexpected number of items in the native control" );
189 void wxHeaderCtrl::DoSetCount(unsigned int count
)
193 // first delete all old columns
194 const unsigned countOld
= GetShownColumnsCount();
195 for ( n
= 0; n
< countOld
; n
++ )
197 if ( !Header_DeleteItem(GetHwnd(), 0) )
199 wxLogLastError(wxT("Header_DeleteItem"));
203 // update the column indices order array before changing m_numColumns
204 DoResizeColumnIndices(m_colIndices
, count
);
206 // and add the new ones
207 m_numColumns
= count
;
208 m_isHidden
.resize(m_numColumns
);
209 for ( n
= 0; n
< count
; n
++ )
211 const wxHeaderColumn
& col
= GetColumn(n
);
214 m_isHidden
[n
] = false;
216 DoInsertItem(col
, n
);
218 else // hidden initially
220 m_isHidden
[n
] = true;
225 void wxHeaderCtrl::DoUpdate(unsigned int idx
)
227 // the native control does provide Header_SetItem() but it's inconvenient
228 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
229 // arrange not to block setting the width from there and the logic would be
230 // more complicated as we'd have to reset the old values as well as setting
231 // the new ones -- so instead just recreate the column
233 const wxHeaderColumn
& col
= GetColumn(idx
);
234 if ( col
.IsHidden() )
236 // column is hidden now
237 if ( !m_isHidden
[idx
] )
239 // but it wasn't hidden before, so remove it
240 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
));
242 m_isHidden
[idx
] = true;
244 //else: nothing to do, updating hidden column doesn't have any effect
246 else // column is shown now
248 if ( m_isHidden
[idx
] )
250 m_isHidden
[idx
] = false;
252 else // and it was shown before as well
254 // we need to remove the old column
255 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
));
258 DoInsertItem(col
, idx
);
262 void wxHeaderCtrl::DoInsertItem(const wxHeaderColumn
& col
, unsigned int idx
)
264 wxASSERT_MSG( !col
.IsHidden(), "should only be called for shown columns" );
268 // notice that we need to store the string we use the pointer to until we
269 // pass it to the control
270 hdi
.mask
|= HDI_TEXT
;
271 wxWxCharBuffer buf
= col
.GetTitle().t_str();
272 hdi
.pszText
= buf
.data();
273 hdi
.cchTextMax
= wxStrlen(buf
);
275 const wxBitmap bmp
= col
.GetBitmap();
278 hdi
.mask
|= HDI_IMAGE
;
282 const int bmpWidth
= bmp
.GetWidth(),
283 bmpHeight
= bmp
.GetHeight();
287 m_imageList
= new wxImageList(bmpWidth
, bmpHeight
);
288 (void) // suppress mingw32 warning about unused computed value
289 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList
));
291 else // already have an image list
293 // check that all bitmaps we use have the same size
296 m_imageList
->GetSize(0, imageWidth
, imageHeight
);
298 wxASSERT_MSG( imageWidth
== bmpWidth
&& imageHeight
== bmpHeight
,
299 "all column bitmaps must have the same size" );
302 m_imageList
->Add(bmp
);
303 hdi
.iImage
= m_imageList
->GetImageCount() - 1;
305 else // no bitmap but we still need to update the item
307 hdi
.iImage
= I_IMAGENONE
;
311 if ( col
.GetAlignment() != wxALIGN_NOT
)
313 hdi
.mask
|= HDI_FORMAT
| HDF_LEFT
;
314 switch ( col
.GetAlignment() )
321 case wxALIGN_CENTER_HORIZONTAL
:
322 hdi
.fmt
|= HDF_CENTER
;
326 hdi
.fmt
|= HDF_RIGHT
;
330 wxFAIL_MSG( "invalid column header alignment" );
334 if ( col
.IsSortKey() )
336 hdi
.mask
|= HDI_FORMAT
;
337 hdi
.fmt
|= col
.IsSortOrderAscending() ? HDF_SORTUP
: HDF_SORTDOWN
;
340 if ( col
.GetWidth() != wxCOL_WIDTH_DEFAULT
)
342 hdi
.mask
|= HDI_WIDTH
;
343 hdi
.cxy
= col
.GetWidth();
346 hdi
.mask
|= HDI_ORDER
;
347 hdi
.iOrder
= MSWToNativeOrder(m_colIndices
.Index(idx
));
349 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM
,
350 MSWToNativeIdx(idx
), (LPARAM
)&hdi
) == -1 )
352 wxLogLastError(wxT("Header_InsertItem()"));
356 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt
& order
)
358 wxArrayInt orderShown
;
359 orderShown
.reserve(m_numColumns
);
361 for ( unsigned n
= 0; n
< m_numColumns
; n
++ )
363 const int idx
= order
[n
];
364 if ( GetColumn(idx
).IsShown() )
365 orderShown
.push_back(MSWToNativeIdx(idx
));
368 if ( !Header_SetOrderArray(GetHwnd(), orderShown
.size(), &orderShown
[0]) )
370 wxLogLastError(wxT("Header_GetOrderArray"));
373 m_colIndices
= order
;
376 wxArrayInt
wxHeaderCtrl::DoGetColumnsOrder() const
378 // we don't use Header_GetOrderArray() here because it doesn't return
379 // information about the hidden columns, instead we just save the columns
380 // order array in DoSetColumnsOrder() and update it when they're reordered
384 // ----------------------------------------------------------------------------
385 // wxHeaderCtrl indexes and positions translation
386 // ----------------------------------------------------------------------------
388 int wxHeaderCtrl::MSWToNativeIdx(int idx
)
390 // don't check for GetColumn(idx).IsShown() as it could have just became
391 // false and we may be called from DoUpdate() to delete the old column
392 wxASSERT_MSG( !m_isHidden
[idx
],
393 "column must be visible to have an "
394 "index in the native control" );
397 for ( int i
= 0; i
< idx
; i
++ )
399 if ( GetColumn(i
).IsHidden() )
400 item
--; // one less column the native control knows about
403 wxASSERT_MSG( item
>= 0 && item
<= GetShownColumnsCount(), "logic error" );
408 int wxHeaderCtrl::MSWFromNativeIdx(int item
)
410 wxASSERT_MSG( item
>= 0 && item
< GetShownColumnsCount(),
411 "column index out of range" );
413 // reverse the above function
416 for ( unsigned n
= 0; n
< m_numColumns
; n
++ )
421 if ( GetColumn(n
).IsHidden() )
425 wxASSERT_MSG( MSWToNativeIdx(idx
) == item
, "logic error" );
430 int wxHeaderCtrl::MSWToNativeOrder(int pos
)
432 wxASSERT_MSG( pos
>= 0 && static_cast<unsigned>(pos
) < m_numColumns
,
433 "column position out of range" );
436 for ( int n
= 0; n
< pos
; n
++ )
438 if ( GetColumn(m_colIndices
[n
]).IsHidden() )
442 wxASSERT_MSG( order
>= 0 && order
<= GetShownColumnsCount(), "logic error" );
447 int wxHeaderCtrl::MSWFromNativeOrder(int order
)
449 wxASSERT_MSG( order
>= 0 && order
< GetShownColumnsCount(),
450 "native column position out of range" );
452 unsigned pos
= order
;
453 for ( unsigned n
= 0; n
< m_numColumns
; n
++ )
458 if ( GetColumn(m_colIndices
[n
]).IsHidden() )
462 wxASSERT_MSG( MSWToNativeOrder(pos
) == order
, "logic error" );
467 // ----------------------------------------------------------------------------
468 // wxHeaderCtrl events
469 // ----------------------------------------------------------------------------
471 wxEventType
wxHeaderCtrl::GetClickEventType(bool dblclk
, int button
)
477 evtType
= dblclk
? wxEVT_HEADER_DCLICK
478 : wxEVT_HEADER_CLICK
;
482 evtType
= dblclk
? wxEVT_HEADER_RIGHT_DCLICK
483 : wxEVT_HEADER_RIGHT_CLICK
;
487 evtType
= dblclk
? wxEVT_HEADER_MIDDLE_DCLICK
488 : wxEVT_HEADER_MIDDLE_CLICK
;
492 wxFAIL_MSG( wxS("unexpected event type") );
493 evtType
= wxEVT_NULL
;
499 bool wxHeaderCtrl::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM
*result
)
501 NMHEADER
* const nmhdr
= (NMHEADER
*)lParam
;
503 wxEventType evtType
= wxEVT_NULL
;
507 const UINT code
= nmhdr
->hdr
.code
;
509 // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE
510 // so only access for header control events (and yes, the direction of
511 // comparisons with FIRST/LAST is correct even if it seems inverted)
512 int idx
= code
<= HDN_FIRST
&& code
> HDN_LAST
? nmhdr
->iItem
: -1;
515 // we also get bogus HDN_BEGINDRAG with -1 index so don't call
516 // MSWFromNativeIdx() unconditionally for nmhdr->iItem
517 idx
= MSWFromNativeIdx(idx
);
526 case HDN_ITEMDBLCLICK
:
527 evtType
= GetClickEventType(code
== HDN_ITEMDBLCLICK
, nmhdr
->iButton
);
529 // We're not dragging any more.
530 m_colBeingDragged
= -1;
533 // although we should get the notifications about the right clicks
534 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
535 // happen in practice on any Windows system up to 2003
540 idx
= wxMSWGetColumnClicked(&nmhdr
->hdr
, &pt
);
541 if ( idx
!= wxNOT_FOUND
)
543 idx
= MSWFromNativeIdx(idx
);
545 // due to a bug in mingw32 headers NM_RDBLCLK is signed
546 // there so we need a cast to avoid warnings about signed/
547 // unsigned comparison
548 evtType
= GetClickEventType(
549 code
== static_cast<UINT
>(NM_RDBLCLK
), 1);
551 //else: ignore clicks outside any column
555 case HDN_DIVIDERDBLCLICK
:
556 evtType
= wxEVT_HEADER_SEPARATOR_DCLICK
;
560 // column resizing events
561 // ----------------------
563 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
564 // ASCII and Unicode versions of this message
565 case HDN_BEGINTRACKA
:
566 case HDN_BEGINTRACKW
:
567 // non-resizable columns can't be resized no matter what, don't
568 // even generate any events for them
569 if ( !GetColumn(idx
).IsResizeable() )
575 evtType
= wxEVT_HEADER_BEGIN_RESIZE
;
580 width
= nmhdr
->pitem
->cxy
;
582 if ( evtType
== wxEVT_NULL
)
584 evtType
= wxEVT_HEADER_END_RESIZE
;
586 // don't generate events with invalid width
587 const int minWidth
= GetColumn(idx
).GetMinWidth();
588 if ( width
< minWidth
)
593 // The control is not supposed to send HDN_TRACK when using
594 // HDS_FULLDRAG (which we do use) but apparently some versions of
595 // comctl32.dll still do it, see #13506, so catch both messages
596 // just in case we are dealing with one of these buggy versions.
598 case HDN_ITEMCHANGING
:
599 if ( nmhdr
->pitem
&& (nmhdr
->pitem
->mask
& HDI_WIDTH
) )
601 // prevent the column from being shrunk beneath its min width
602 width
= nmhdr
->pitem
->cxy
;
603 if ( width
< GetColumn(idx
).GetMinWidth() )
605 // don't generate any events and prevent the change from
609 else // width is acceptable
611 // generate the resizing event from here as we don't seem
612 // to be getting HDN_TRACK events at all, at least with
614 evtType
= wxEVT_HEADER_RESIZING
;
620 // column reordering events
621 // ------------------------
624 // Windows sometimes sends us events with invalid column indices
625 if ( nmhdr
->iItem
== -1 )
628 // If we are dragging a column that is not draggable and the mouse
629 // is moved over a different column then we get the column number from
630 // the column under the mouse. This results in an unexpected behaviour
631 // if this column is draggable. To prevent this remember the column we
632 // are dragging for the complete drag and drop cycle.
633 if ( m_colBeingDragged
== -1 )
635 m_colBeingDragged
= idx
;
638 // column must have the appropriate flag to be draggable
639 if ( !GetColumn(m_colBeingDragged
).IsReorderable() )
645 evtType
= wxEVT_HEADER_BEGIN_REORDER
;
649 wxASSERT_MSG( nmhdr
->pitem
->mask
& HDI_ORDER
, "should have order" );
650 order
= nmhdr
->pitem
->iOrder
;
652 // we also get messages with invalid order when column reordering
653 // is cancelled (e.g. by pressing Esc)
657 order
= MSWFromNativeOrder(order
);
659 evtType
= wxEVT_HEADER_END_REORDER
;
661 // We (successfully) ended dragging the column.
662 m_colBeingDragged
= -1;
665 case NM_RELEASEDCAPTURE
:
666 evtType
= wxEVT_HEADER_DRAGGING_CANCELLED
;
668 // Dragging the column was cancelled.
669 m_colBeingDragged
= -1;
674 // do generate the corresponding wx event
675 if ( evtType
!= wxEVT_NULL
)
677 wxHeaderCtrlEvent
event(evtType
, GetId());
678 event
.SetEventObject(this);
679 event
.SetColumn(idx
);
680 event
.SetWidth(width
);
682 event
.SetNewOrder(order
);
684 const bool processed
= GetEventHandler()->ProcessEvent(event
);
686 if ( processed
&& !event
.IsAllowed() )
691 // special post-processing for HDN_ENDDRAG: we need to update the
692 // internal column indices array if this is allowed to go ahead as
693 // the native control is going to reorder its columns now
694 if ( evtType
== wxEVT_HEADER_END_REORDER
)
695 MoveColumnInOrderArray(m_colIndices
, idx
, order
);
699 // skip default processing below
707 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
708 // interpret TRUE return value as meaning to stop the control
709 // default handling of the message
715 return wxHeaderCtrlBase::MSWOnNotify(idCtrl
, lParam
, result
);
718 #endif // wxHAS_GENERIC_HEADERCTRL
720 #endif // wxUSE_HEADERCTRL