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()
57 bool wxHeaderCtrl::Create(wxWindow
*parent
,
64 // notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
65 // here as we already call InitCommonControls() in wxApp initialization
66 // code which covers this
68 if ( !CreateControl(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) )
71 if ( !MSWCreateControl(WC_HEADER
, _T(""), pos
, size
) )
77 WXDWORD
wxHeaderCtrl::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
79 WXDWORD msStyle
= wxControl::MSWGetStyle(style
, exstyle
);
81 if ( style
& wxHD_DRAGDROP
)
82 msStyle
|= HDS_DRAGDROP
;
84 // the control looks nicer with these styles and there doesn't seem to be
85 // any reason to not use them so we always do (as for HDS_HORZ it is 0
86 // anyhow but include it for clarity)
87 msStyle
|= HDS_HORZ
| HDS_BUTTONS
| HDS_FLAT
| HDS_FULLDRAG
| HDS_HOTTRACK
;
92 wxHeaderCtrl::~wxHeaderCtrl()
97 // ----------------------------------------------------------------------------
98 // wxHeaderCtrl scrolling
99 // ----------------------------------------------------------------------------
101 void wxHeaderCtrl::DoSetSize(int x
, int y
,
105 wxHeaderCtrlBase::DoSetSize(x
+ m_scrollOffset
, y
, w
- m_scrollOffset
, h
,
109 void wxHeaderCtrl::DoScrollHorz(int dx
)
111 // as the native control doesn't support offsetting its contents, we use a
112 // hack here to make it appear correctly when the parent is scrolled:
113 // instead of scrolling or repainting we simply move the control window
114 // itself: to be precise, offset it by the scroll increment to the left and
115 // increment its width to still extend to the right boundary to compensate
116 // for it (notice that dx is negative when scrolling to the right)
117 m_scrollOffset
+= dx
;
119 wxHeaderCtrlBase::DoSetSize(GetPosition().x
+ dx
, -1,
120 GetSize().x
- dx
, -1,
121 wxSIZE_USE_EXISTING
);
124 // ----------------------------------------------------------------------------
125 // wxHeaderCtrl geometry calculation
126 // ----------------------------------------------------------------------------
128 wxSize
wxHeaderCtrl::DoGetBestSize() const
130 RECT rc
= wxGetClientRect(GetHwndOf(GetParent()));
132 HDLAYOUT layout
= { &rc
, &wpos
};
133 if ( !Header_Layout(GetHwnd(), &layout
) )
135 wxLogLastError(_T("Header_Layout"));
136 return wxControl::DoGetBestSize();
139 return wxSize(wpos
.cx
, wpos
.cy
);
142 // ----------------------------------------------------------------------------
143 // wxHeaderCtrl columns managements
144 // ----------------------------------------------------------------------------
146 unsigned int wxHeaderCtrl::DoGetCount() const
148 // we can't use Header_GetItemCount() here because it doesn't take the
149 // hidden columns into account and we can't find the hidden columns after
150 // the last shown one in MSWFromNativeIdx() without knowing where to stop
151 // so we have to store the columns count internally
155 int wxHeaderCtrl::GetShownColumnsCount() const
157 const int numItems
= Header_GetItemCount(GetHwnd());
159 wxASSERT_MSG( numItems
>= 0 && (unsigned)numItems
<= m_numColumns
,
160 "unexpected number of items in the native control" );
165 void wxHeaderCtrl::DoSetCount(unsigned int count
)
169 // first delete all old columns
170 const unsigned countOld
= GetShownColumnsCount();
171 for ( n
= 0; n
< countOld
; n
++ )
173 if ( !Header_DeleteItem(GetHwnd(), 0) )
175 wxLogLastError(_T("Header_DeleteItem"));
179 // update the column indices order array before changing m_numColumns
180 DoResizeColumnIndices(m_colIndices
, count
);
182 // and add the new ones
183 m_numColumns
= count
;
184 m_isHidden
.resize(m_numColumns
);
185 for ( n
= 0; n
< count
; n
++ )
187 const wxHeaderColumn
& col
= GetColumn(n
);
190 m_isHidden
[n
] = false;
192 DoInsertItem(col
, n
);
194 else // hidden initially
196 m_isHidden
[n
] = true;
201 void wxHeaderCtrl::DoUpdate(unsigned int idx
)
203 // the native control does provide Header_SetItem() but it's inconvenient
204 // to use it because it sends HDN_ITEMCHANGING messages and we'd have to
205 // arrange not to block setting the width from there and the logic would be
206 // more complicated as we'd have to reset the old values as well as setting
207 // the new ones -- so instead just recreate the column
209 const wxHeaderColumn
& col
= GetColumn(idx
);
210 if ( col
.IsHidden() )
212 // column is hidden now
213 if ( !m_isHidden
[idx
] )
215 // but it wasn't hidden before, so remove it
216 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
));
218 m_isHidden
[idx
] = true;
220 //else: nothing to do, updating hidden column doesn't have any effect
222 else // column is shown now
224 if ( m_isHidden
[idx
] )
226 m_isHidden
[idx
] = false;
228 else // and it was shown before as well
230 // we need to remove the old column
231 Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx
));
234 DoInsertItem(col
, idx
);
238 void wxHeaderCtrl::DoInsertItem(const wxHeaderColumn
& col
, unsigned int idx
)
240 wxASSERT_MSG( !col
.IsHidden(), "should only be called for shown columns" );
244 // notice that we need to store the string we use the pointer to until we
245 // pass it to the control
246 hdi
.mask
|= HDI_TEXT
;
247 wxWxCharBuffer buf
= col
.GetTitle().wx_str();
248 hdi
.pszText
= buf
.data();
249 hdi
.cchTextMax
= wxStrlen(buf
);
251 const wxBitmap bmp
= col
.GetBitmap();
254 hdi
.mask
|= HDI_IMAGE
;
258 const int bmpWidth
= bmp
.GetWidth(),
259 bmpHeight
= bmp
.GetHeight();
263 m_imageList
= new wxImageList(bmpWidth
, bmpHeight
);
264 Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList
));
266 else // already have an image list
268 // check that all bitmaps we use have the same size
271 m_imageList
->GetSize(0, imageWidth
, imageHeight
);
273 wxASSERT_MSG( imageWidth
== bmpWidth
&& imageHeight
== bmpHeight
,
274 "all column bitmaps must have the same size" );
277 m_imageList
->Add(bmp
);
278 hdi
.iImage
= m_imageList
->GetImageCount() - 1;
280 else // no bitmap but we still need to update the item
282 hdi
.iImage
= I_IMAGENONE
;
286 if ( col
.GetAlignment() != wxALIGN_NOT
)
288 hdi
.mask
|= HDI_FORMAT
;
289 switch ( col
.GetAlignment() )
296 case wxALIGN_CENTER_HORIZONTAL
:
297 hdi
.fmt
|= HDF_CENTER
;
301 hdi
.fmt
|= HDF_RIGHT
;
305 wxFAIL_MSG( "invalid column header alignment" );
309 if ( col
.IsSortKey() )
311 hdi
.mask
|= HDI_FORMAT
;
312 hdi
.fmt
|= col
.IsSortOrderAscending() ? HDF_SORTUP
: HDF_SORTDOWN
;
315 if ( col
.GetWidth() != wxCOL_WIDTH_DEFAULT
)
317 hdi
.mask
|= HDI_WIDTH
;
318 hdi
.cxy
= col
.GetWidth();
321 hdi
.mask
|= HDI_ORDER
;
322 hdi
.iOrder
= m_colIndices
.Index(idx
);
324 if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM
,
325 MSWToNativeIdx(idx
), (LPARAM
)&hdi
) == -1 )
327 wxLogLastError(_T("Header_InsertItem()"));
331 void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt
& order
)
333 wxArrayInt orderShown
;
334 orderShown
.reserve(m_numColumns
);
336 for ( unsigned n
= 0; n
< m_numColumns
; n
++ )
338 const int idx
= order
[n
];
339 if ( GetColumn(idx
).IsShown() )
340 orderShown
.push_back(MSWToNativeIdx(idx
));
343 if ( !Header_SetOrderArray(GetHwnd(), orderShown
.size(), &orderShown
[0]) )
345 wxLogLastError(_T("Header_GetOrderArray"));
348 m_colIndices
= order
;
351 wxArrayInt
wxHeaderCtrl::DoGetColumnsOrder() const
353 // we don't use Header_GetOrderArray() here because it doesn't return
354 // information about the hidden columns, instead we just save the columns
355 // order array in DoSetColumnsOrder() and update it when they're reordered
359 int wxHeaderCtrl::MSWToNativeIdx(int idx
)
361 // don't check for GetColumn(idx).IsShown() as it could have just became
362 // false and we may be called from DoUpdate() to delete the old column
363 wxASSERT_MSG( !m_isHidden
[idx
],
364 "column must be visible to have an "
365 "index in the native control" );
367 for ( int i
= 0; i
< idx
; i
++ )
369 if ( GetColumn(i
).IsHidden() )
370 idx
--; // one less column the native control knows about
376 int wxHeaderCtrl::MSWFromNativeIdx(int item
)
378 wxASSERT_MSG( item
>= 0 && item
< GetShownColumnsCount(),
379 "column index out of range" );
381 // reverse the above function
382 for ( int i
= 0; i
<= item
; i
++ )
384 if ( GetColumn(i
).IsHidden() )
391 int wxHeaderCtrl::MSWFromNativeOrder(int order
)
393 wxASSERT_MSG( order
>= 0 && order
< GetShownColumnsCount(),
394 "column position out of range" );
396 // notice that the loop condition is inclusive because if the column
397 // exactly at position order is hidden we should go to the next one
398 for ( int pos
= 0; pos
<= order
; pos
++ )
400 if ( GetColumn(m_colIndices
[pos
]).IsHidden() )
402 // order can't become greater than m_numColumns here because it is
403 // less than the number of shown columns initially and it is going
404 // to be incremented at most once for every hidden column and
405 // m_numColumns is the total columns number
413 // ----------------------------------------------------------------------------
414 // wxHeaderCtrl events
415 // ----------------------------------------------------------------------------
417 wxEventType
wxHeaderCtrl::GetClickEventType(bool dblclk
, int button
)
423 evtType
= dblclk
? wxEVT_COMMAND_HEADER_DCLICK
424 : wxEVT_COMMAND_HEADER_CLICK
;
428 evtType
= dblclk
? wxEVT_COMMAND_HEADER_RIGHT_DCLICK
429 : wxEVT_COMMAND_HEADER_RIGHT_CLICK
;
433 evtType
= dblclk
? wxEVT_COMMAND_HEADER_MIDDLE_DCLICK
434 : wxEVT_COMMAND_HEADER_MIDDLE_CLICK
;
438 wxFAIL_MSG( wxS("unexpected event type") );
439 evtType
= wxEVT_NULL
;
445 bool wxHeaderCtrl::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM
*result
)
447 NMHEADER
* const nmhdr
= (NMHEADER
*)lParam
;
449 wxEventType evtType
= wxEVT_NULL
;
453 const UINT code
= nmhdr
->hdr
.code
;
455 // we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE
456 // so only access for header control events (and yes, the direction of
457 // comparisons with FIRST/LAST is correct even if it seems inverted)
458 int idx
= code
<= HDN_FIRST
&& code
> HDN_LAST
? nmhdr
->iItem
: -1;
461 // we also get bogus HDN_BEGINDRAG with -1 index so don't call
462 // MSWFromNativeIdx() unconditionally for nmhdr->iItem
463 idx
= MSWFromNativeIdx(idx
);
472 case HDN_ITEMDBLCLICK
:
473 evtType
= GetClickEventType(code
== HDN_ITEMDBLCLICK
, nmhdr
->iButton
);
476 // although we should get the notifications about the right clicks
477 // via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
478 // happen in practice on any Windows system up to 2003
483 idx
= wxMSWGetColumnClicked(&nmhdr
->hdr
, &pt
);
484 if ( idx
!= wxNOT_FOUND
)
486 idx
= MSWFromNativeIdx(idx
);
487 evtType
= GetClickEventType(code
== NM_RDBLCLK
, 1);
489 //else: ignore clicks outside any column
493 case HDN_DIVIDERDBLCLICK
:
494 evtType
= wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK
;
498 // column resizing events
499 // ----------------------
501 // see comments in wxListCtrl::MSWOnNotify() for why we catch both
502 // ASCII and Unicode versions of this message
503 case HDN_BEGINTRACKA
:
504 case HDN_BEGINTRACKW
:
505 // non-resizeable columns can't be resized no matter what, don't
506 // even generate any events for them
507 if ( !GetColumn(idx
).IsResizeable() )
513 evtType
= wxEVT_COMMAND_HEADER_BEGIN_RESIZE
;
518 width
= nmhdr
->pitem
->cxy
;
520 if ( evtType
== wxEVT_NULL
)
522 evtType
= wxEVT_COMMAND_HEADER_END_RESIZE
;
524 // don't generate events with invalid width
525 const int minWidth
= GetColumn(idx
).GetMinWidth();
526 if ( width
< minWidth
)
531 case HDN_ITEMCHANGING
:
532 if ( nmhdr
->pitem
&& (nmhdr
->pitem
->mask
& HDI_WIDTH
) )
534 // prevent the column from being shrunk beneath its min width
535 width
= nmhdr
->pitem
->cxy
;
536 if ( width
< GetColumn(idx
).GetMinWidth() )
538 // don't generate any events and prevent the change from
542 else // width is acceptable
544 // generate the resizing event from here as we don't seem
545 // to be getting HDN_TRACK events at all, at least with
547 evtType
= wxEVT_COMMAND_HEADER_RESIZING
;
553 // column reordering events
554 // ------------------------
557 // Windows sometimes sends us events with invalid column indices
558 if ( nmhdr
->iItem
== -1 )
561 // column must have the appropriate flag to be draggable
562 if ( !GetColumn(idx
).IsReorderable() )
568 evtType
= wxEVT_COMMAND_HEADER_BEGIN_REORDER
;
572 wxASSERT_MSG( nmhdr
->pitem
->mask
& HDI_ORDER
, "should have order" );
573 order
= nmhdr
->pitem
->iOrder
;
575 // we also get messages with invalid order when column reordering
576 // is cancelled (e.g. by pressing Esc)
580 order
= MSWFromNativeOrder(order
);
582 evtType
= wxEVT_COMMAND_HEADER_END_REORDER
;
585 case NM_RELEASEDCAPTURE
:
586 evtType
= wxEVT_COMMAND_HEADER_DRAGGING_CANCELLED
;
591 // do generate the corresponding wx event
592 if ( evtType
!= wxEVT_NULL
)
594 wxHeaderCtrlEvent
event(evtType
, GetId());
595 event
.SetEventObject(this);
596 event
.SetColumn(idx
);
597 event
.SetWidth(width
);
599 event
.SetNewOrder(order
);
601 if ( GetEventHandler()->ProcessEvent(event
) )
603 if ( event
.IsAllowed() )
604 return true; // skip default message handling below
606 // we need to veto the default handling of this message, don't
607 // return to execute the code in the "if veto" branch below
610 else // not processed
612 // special post-processing for HDN_ENDDRAG: we need to update the
613 // internal column indices array if this is allowed to go ahead as
614 // the native control is going to reorder its columns now
615 if ( evtType
== wxEVT_COMMAND_HEADER_END_REORDER
)
616 MoveColumnInOrderArray(m_colIndices
, idx
, order
);
622 // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
623 // interpret TRUE return value as meaning to stop the control
624 // default handling of the message
630 return wxHeaderCtrlBase::MSWOnNotify(idCtrl
, lParam
, result
);
633 #endif // wxHAS_GENERIC_HEADERCTRL