Files
wxWidgets/src/msw/headerctrl.cpp
Maarten Bent fd9df06d35 Fix applying header style of wxMSWHeaderCtrl
Call SetWindowStyleFlag when creating the control. Apply wxHD_BITMAP_ON_RIGHT
style. Get rid of the helper function.
2020-01-19 14:55:39 +01:00

1158 lines
36 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/msw/headerctrl.cpp
// Purpose: implementation of wxHeaderCtrl for wxMSW
// Author: Vadim Zeitlin
// Created: 2008-12-01
// Copyright: (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_HEADERCTRL
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/log.h"
#endif // WX_PRECOMP
#include "wx/headerctrl.h"
#ifndef wxHAS_GENERIC_HEADERCTRL
#include "wx/imaglist.h"
#include "wx/msw/wrapcctl.h"
#include "wx/msw/private.h"
#include "wx/msw/private/customdraw.h"
#include "wx/msw/private/winstyle.h"
#ifndef HDM_SETBITMAPMARGIN
#define HDM_SETBITMAPMARGIN 0x1234
#endif
#ifndef Header_SetBitmapMargin
#define Header_SetBitmapMargin(hwnd, margin) \
::SendMessage((hwnd), HDM_SETBITMAPMARGIN, (WPARAM)(margin), 0)
#endif
// from src/msw/listctrl.cpp
extern int WXDLLIMPEXP_CORE wxMSWGetColumnClicked(NMHDR *nmhdr, POINT *ptClick);
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrlCustomDraw: our custom draw helper
// ----------------------------------------------------------------------------
class wxMSWHeaderCtrlCustomDraw : public wxMSWImpl::CustomDraw
{
public:
wxMSWHeaderCtrlCustomDraw()
{
}
// Make this field public to let wxHeaderCtrl update it directly when its
// attributes change.
wxItemAttr m_attr;
private:
virtual bool HasCustomDrawnItems() const wxOVERRIDE
{
// We only exist if the header does need to be custom drawn.
return true;
}
virtual const wxItemAttr*
GetItemAttr(DWORD_PTR WXUNUSED(dwItemSpec)) const wxOVERRIDE
{
// We use the same attribute for all items for now.
return &m_attr;
}
};
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl: the native header control
// ----------------------------------------------------------------------------
class wxMSWHeaderCtrl : public wxControl
{
public:
explicit wxMSWHeaderCtrl(wxHeaderCtrl& header) :
m_header(header)
{
Init();
}
bool Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name);
virtual ~wxMSWHeaderCtrl();
// Override to implement colours support via custom drawing.
virtual bool SetBackgroundColour(const wxColour& colour) wxOVERRIDE;
virtual bool SetForegroundColour(const wxColour& colour) wxOVERRIDE;
virtual bool SetFont(const wxFont& font) wxOVERRIDE;
// The implementation of wxHeaderCtrlBase virtual functions
void SetCount(unsigned int count);
unsigned int GetCount() const;
void UpdateHeader(unsigned int idx);
void ScrollHorz(int dx);
void SetColumnsOrder(const wxArrayInt& order);
wxArrayInt GetColumnsOrder() const;
protected:
// override wxWindow methods which must be implemented by a new control
virtual wxSize DoGetBestSize() const wxOVERRIDE;
virtual void DoSetSize(int x, int y,
int width, int height,
int sizeFlags = wxSIZE_AUTO) wxOVERRIDE;
virtual void MSWUpdateFontOnDPIChange(const wxSize& newDPI) wxOVERRIDE;
private:
// override MSW-specific methods needed for new control
virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const wxOVERRIDE;
virtual bool MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) wxOVERRIDE;
// common part of all ctors
void Init();
// wrapper around Header_InsertItem(): insert the item using information
// from the given column at the given index
void DoInsertItem(const wxHeaderColumn& col, unsigned int idx);
// get the number of currently visible items: this is also the total number
// of items contained in the native control
int GetShownColumnsCount() const;
// due to the discrepancy for the hidden columns which we know about but
// the native control does not, there can be a difference between the
// column indices we use and the ones used by the native control; these
// functions translate between them
//
// notice that MSWToNativeIdx() shouldn't be called for hidden columns and
// MSWFromNativeIdx() always returns an index of a visible column
int MSWToNativeIdx(int idx);
int MSWFromNativeIdx(int item);
// this is the same as above but for order, not index
int MSWToNativeOrder(int order);
int MSWFromNativeOrder(int order);
// get the event type corresponding to a click or double click event
// (depending on dblclk value) with the specified (using MSW convention)
// mouse button
wxEventType GetClickEventType(bool dblclk, int button);
// allocate m_customDraw if we need it or free it if it no longer is,
// return the pointer which can be used to update it if it's non-null
wxMSWHeaderCtrlCustomDraw* GetCustomDraw();
// the real wxHeaderCtrl control
wxHeaderCtrl &m_header;
// the number of columns in the control, including the hidden ones (not
// taken into account by the native control, see comment in DoGetCount())
unsigned int m_numColumns;
// this is a lookup table allowing us to check whether the column with the
// given index is currently shown in the native control, in which case the
// value of this array element with this index is 0, or hidden
//
// notice that this may be different from GetColumn(idx).IsHidden() and in
// fact we need this array precisely because it will be different from it
// in DoUpdate() when the column hidden flag gets toggled and we need it to
// handle this transition correctly
wxArrayInt m_isHidden;
// the order of our columns: this array contains the index of the column
// shown at the position n as the n-th element
//
// this is necessary only to handle the hidden columns: the native control
// doesn't know about them and so we can't use Header_GetOrderArray()
wxArrayInt m_colIndices;
// the image list: initially NULL, created on demand
wxImageList *m_imageList;
// the offset of the window used to emulate scrolling it
int m_scrollOffset;
// actual column we are dragging or -1 if not dragging anything
int m_colBeingDragged;
// a column is currently being resized
bool m_isColBeingResized;
// the custom draw helper: initially NULL, created on demand, use
// GetCustomDraw() to do it
wxMSWHeaderCtrlCustomDraw *m_customDraw;
};
// ============================================================================
// wxMSWHeaderCtrl implementation
// ============================================================================
extern WXDLLIMPEXP_DATA_CORE(const char) wxMSWHeaderCtrlNameStr[] = "wxMSWHeaderCtrl";
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl construction/destruction
// ----------------------------------------------------------------------------
void wxMSWHeaderCtrl::Init()
{
m_numColumns = 0;
m_imageList = NULL;
m_scrollOffset = 0;
m_colBeingDragged = -1;
m_isColBeingResized = false;
m_customDraw = NULL;
}
bool wxMSWHeaderCtrl::Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name)
{
// notice that we don't need InitCommonControlsEx(ICC_LISTVIEW_CLASSES)
// here as we already call InitCommonControls() in wxApp initialization
// code which covers this
if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
return false;
if ( !MSWCreateControl(WC_HEADER, wxT(""), pos, size) )
return false;
// special hack for margins when using comctl32.dll v6 or later: the
// default margin is too big and results in label truncation when the
// column width is just about right to show it together with the sort
// indicator, so reduce it to a smaller value (in principle we could even
// use 0 here but this starts to look ugly)
if ( wxApp::GetComCtl32Version() >= 600 )
{
(void)Header_SetBitmapMargin(GetHwnd(), wxGetSystemMetrics(SM_CXEDGE, parent));
}
return true;
}
WXDWORD wxMSWHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
{
WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);
if ( style & wxHD_ALLOW_REORDER )
msStyle |= HDS_DRAGDROP;
// the control looks nicer with these styles and there doesn't seem to be
// any reason to not use them so we always do (as for HDS_HORZ it is 0
// anyhow but include it for clarity)
// NOTE: don't use however HDS_FLAT because it makes the control look
// non-native when running WinXP in classic mode
msStyle |= HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | HDS_HOTTRACK;
return msStyle;
}
wxMSWHeaderCtrl::~wxMSWHeaderCtrl()
{
delete m_imageList;
delete m_customDraw;
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl scrolling
// ----------------------------------------------------------------------------
void wxMSWHeaderCtrl::DoSetSize(int x, int y,
int w, int h,
int sizeFlags)
{
wxControl::DoSetSize(x + m_scrollOffset, y, w - m_scrollOffset, h,
sizeFlags & wxSIZE_FORCE);
}
void wxMSWHeaderCtrl::ScrollHorz(int dx)
{
// as the native control doesn't support offsetting its contents, we use a
// hack here to make it appear correctly when the parent is scrolled:
// instead of scrolling or repainting we simply move the control window
// itself: to be precise, offset it by the scroll increment to the left and
// increment its width to still extend to the right boundary to compensate
// for it (notice that dx is negative when scrolling to the right)
m_scrollOffset += dx;
wxControl::DoSetSize(GetPosition().x + dx, -1,
GetSize().x - dx, -1,
wxSIZE_USE_EXISTING);
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl geometry calculation
// ----------------------------------------------------------------------------
wxSize wxMSWHeaderCtrl::DoGetBestSize() const
{
RECT rc = wxGetClientRect(GetHwndOf(m_header.GetParent()));
WINDOWPOS wpos;
HDLAYOUT layout = { &rc, &wpos };
if ( !Header_Layout(GetHwnd(), &layout) )
{
wxLogLastError(wxT("Header_Layout"));
return wxControl::DoGetBestSize();
}
return wxSize(wxDefaultCoord, wpos.cy);
}
void wxMSWHeaderCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
{
wxControl::MSWUpdateFontOnDPIChange(newDPI);
if ( wxMSWHeaderCtrlCustomDraw * customDraw = GetCustomDraw() )
{
customDraw->m_attr.SetFont(m_font);
}
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl columns managements
// ----------------------------------------------------------------------------
unsigned int wxMSWHeaderCtrl::GetCount() const
{
// we can't use Header_GetItemCount() here because it doesn't take the
// hidden columns into account and we can't find the hidden columns after
// the last shown one in MSWFromNativeIdx() without knowing where to stop
// so we have to store the columns count internally
return m_numColumns;
}
int wxMSWHeaderCtrl::GetShownColumnsCount() const
{
const int numItems = Header_GetItemCount(GetHwnd());
wxASSERT_MSG( numItems >= 0 && (unsigned)numItems <= m_numColumns,
"unexpected number of items in the native control" );
return numItems;
}
void wxMSWHeaderCtrl::SetCount(unsigned int count)
{
unsigned n;
// first delete all old columns
const unsigned countOld = GetShownColumnsCount();
for ( n = 0; n < countOld; n++ )
{
if ( !Header_DeleteItem(GetHwnd(), 0) )
{
wxLogLastError(wxT("Header_DeleteItem"));
}
}
// update the column indices order array before changing m_numColumns
m_header.DoResizeColumnIndices(m_colIndices, count);
// and add the new ones
m_numColumns = count;
m_isHidden.resize(m_numColumns);
for ( n = 0; n < count; n++ )
{
const wxHeaderColumn& col = m_header.GetColumn(n);
if ( col.IsShown() )
{
m_isHidden[n] = false;
DoInsertItem(col, n);
}
else // hidden initially
{
m_isHidden[n] = true;
}
}
}
void wxMSWHeaderCtrl::UpdateHeader(unsigned int idx)
{
// the native control does provide Header_SetItem() but it's inconvenient
// to use it because it sends HDN_ITEMCHANGING messages and we'd have to
// arrange not to block setting the width from there and the logic would be
// more complicated as we'd have to reset the old values as well as setting
// the new ones -- so instead just recreate the column
const wxHeaderColumn& col = m_header.GetColumn(idx);
if ( col.IsHidden() )
{
// column is hidden now
if ( !m_isHidden[idx] )
{
// but it wasn't hidden before, so remove it
if ( !Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)) )
wxLogLastError(wxS("Header_DeleteItem()"));
m_isHidden[idx] = true;
}
//else: nothing to do, updating hidden column doesn't have any effect
}
else // column is shown now
{
if ( m_isHidden[idx] )
{
m_isHidden[idx] = false;
}
else // and it was shown before as well
{
// we need to remove the old column
if ( !Header_DeleteItem(GetHwnd(), MSWToNativeIdx(idx)) )
wxLogLastError(wxS("Header_DeleteItem()"));
}
DoInsertItem(col, idx);
}
}
void wxMSWHeaderCtrl::DoInsertItem(const wxHeaderColumn& col, unsigned int idx)
{
wxASSERT_MSG( !col.IsHidden(), "should only be called for shown columns" );
wxHDITEM hdi;
// notice that we need to store the string we use the pointer to until we
// pass it to the control
hdi.mask |= HDI_TEXT;
wxWxCharBuffer buf = col.GetTitle().t_str();
hdi.pszText = buf.data();
hdi.cchTextMax = wxStrlen(buf);
const wxBitmap bmp = col.GetBitmap();
if ( bmp.IsOk() )
{
hdi.mask |= HDI_IMAGE;
if ( HasFlag(wxHD_BITMAP_ON_RIGHT) )
hdi.fmt |= HDF_BITMAP_ON_RIGHT;
const int bmpWidth = bmp.GetWidth(),
bmpHeight = bmp.GetHeight();
if ( !m_imageList )
{
m_imageList = new wxImageList(bmpWidth, bmpHeight);
(void) // suppress mingw32 warning about unused computed value
Header_SetImageList(GetHwnd(), GetHimagelistOf(m_imageList));
}
else // already have an image list
{
// check that all bitmaps we use have the same size
int imageWidth,
imageHeight;
m_imageList->GetSize(0, imageWidth, imageHeight);
wxASSERT_MSG( imageWidth == bmpWidth && imageHeight == bmpHeight,
"all column bitmaps must have the same size" );
}
m_imageList->Add(bmp);
hdi.iImage = m_imageList->GetImageCount() - 1;
}
if ( col.GetAlignment() != wxALIGN_NOT )
{
hdi.mask |= HDI_FORMAT;
// wxALIGN_LEFT is the same as wxALIGN_NOT
switch ( col.GetAlignment() )
{
case wxALIGN_CENTER:
case wxALIGN_CENTER_HORIZONTAL:
hdi.fmt |= HDF_CENTER;
break;
case wxALIGN_RIGHT:
hdi.fmt |= HDF_RIGHT;
break;
default:
wxFAIL_MSG( "invalid column header alignment" );
}
}
if ( col.IsSortKey() )
{
hdi.mask |= HDI_FORMAT;
hdi.fmt |= col.IsSortOrderAscending() ? HDF_SORTUP : HDF_SORTDOWN;
}
if ( col.GetWidth() != wxCOL_WIDTH_DEFAULT )
{
hdi.mask |= HDI_WIDTH;
hdi.cxy = col.GetWidth();
}
hdi.mask |= HDI_ORDER;
hdi.iOrder = MSWToNativeOrder(m_colIndices.Index(idx));
if ( ::SendMessage(GetHwnd(), HDM_INSERTITEM,
MSWToNativeIdx(idx), (LPARAM)&hdi) == -1 )
{
wxLogLastError(wxT("Header_InsertItem()"));
}
// Resizing cursor that correctly reflects per-column IsResizable() cannot
// be implemented, it is per-control rather than per-column in the native
// control. Enable resizing cursor if at least one column is resizeble.
bool hasResizableColumns = false;
for ( unsigned n = 0; n < m_header.GetColumnCount(); n++ )
{
const wxHeaderColumn& c = m_header.GetColumn(n);
if (c.IsShown() && c.IsResizeable())
{
hasResizableColumns = true;
break;
}
}
wxMSWWinStyleUpdater(GetHwnd())
.TurnOnOrOff(!hasResizableColumns, HDS_NOSIZING);
}
void wxMSWHeaderCtrl::SetColumnsOrder(const wxArrayInt& order)
{
// This can happen if we don't have any columns at all and "order" is empty
// anyhow in this case, so we don't have anything to do (note that we
// already know that the input array contains m_numColumns elements, as
// it's checked by the public SetColumnsOrder()).
if ( !m_numColumns )
return;
wxArrayInt orderShown;
orderShown.reserve(m_numColumns);
for ( unsigned n = 0; n < m_numColumns; n++ )
{
const int idx = order[n];
if ( m_header.GetColumn(idx).IsShown() )
orderShown.push_back(MSWToNativeIdx(idx));
}
if ( !Header_SetOrderArray(GetHwnd(), orderShown.size(), &orderShown[0]) )
{
wxLogLastError(wxT("Header_GetOrderArray"));
}
m_colIndices = order;
}
wxArrayInt wxMSWHeaderCtrl::GetColumnsOrder() const
{
// we don't use Header_GetOrderArray() here because it doesn't return
// information about the hidden columns, instead we just save the columns
// order array in DoSetColumnsOrder() and update it when they're reordered
return m_colIndices;
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl indexes and positions translation
// ----------------------------------------------------------------------------
int wxMSWHeaderCtrl::MSWToNativeIdx(int idx)
{
// don't check for GetColumn(idx).IsShown() as it could have just became
// false and we may be called from DoUpdate() to delete the old column
wxASSERT_MSG( !m_isHidden[idx],
"column must be visible to have an "
"index in the native control" );
int item = idx;
for ( int i = 0; i < idx; i++ )
{
if ( m_header.GetColumn(i).IsHidden() )
item--; // one less column the native control knows about
}
wxASSERT_MSG( item >= 0 && item <= GetShownColumnsCount(), "logic error" );
return item;
}
int wxMSWHeaderCtrl::MSWFromNativeIdx(int item)
{
wxASSERT_MSG( item >= 0 && item < GetShownColumnsCount(),
"column index out of range" );
// reverse the above function
unsigned idx = item;
for ( unsigned n = 0; n < m_numColumns; n++ )
{
if ( n > idx )
break;
if ( m_header.GetColumn(n).IsHidden() )
idx++;
}
wxASSERT_MSG( MSWToNativeIdx(idx) == item, "logic error" );
return idx;
}
int wxMSWHeaderCtrl::MSWToNativeOrder(int pos)
{
wxASSERT_MSG( pos >= 0 && static_cast<unsigned>(pos) < m_numColumns,
"column position out of range" );
int order = pos;
for ( int n = 0; n < pos; n++ )
{
if ( m_header.GetColumn(m_colIndices[n]).IsHidden() )
order--;
}
wxASSERT_MSG( order >= 0 && order <= GetShownColumnsCount(), "logic error" );
return order;
}
int wxMSWHeaderCtrl::MSWFromNativeOrder(int order)
{
wxASSERT_MSG( order >= 0 && order < GetShownColumnsCount(),
"native column position out of range" );
unsigned pos = order;
for ( unsigned n = 0; n < m_numColumns; n++ )
{
if ( n > pos )
break;
if ( m_header.GetColumn(m_colIndices[n]).IsHidden() )
pos++;
}
wxASSERT_MSG( MSWToNativeOrder(pos) == order, "logic error" );
return pos;
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl appearance
// ----------------------------------------------------------------------------
wxMSWHeaderCtrlCustomDraw* wxMSWHeaderCtrl::GetCustomDraw()
{
// There is no need to make the control custom drawn just because it has a
// custom font, the native control handles the font just fine on its own,
// so if our custom colours were reset, don't bother with custom drawing
// any longer.
if ( !m_hasBgCol && !m_hasFgCol )
{
if ( m_customDraw )
{
delete m_customDraw;
m_customDraw = NULL;
}
return NULL;
}
// We do have at least one custom colour, so enable custom drawing.
if ( !m_customDraw )
m_customDraw = new wxMSWHeaderCtrlCustomDraw();
return m_customDraw;
}
bool wxMSWHeaderCtrl::SetBackgroundColour(const wxColour& colour)
{
if ( !wxControl::SetBackgroundColour(colour) )
return false;
if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
{
customDraw->m_attr.SetBackgroundColour(colour);
}
return true;
}
bool wxMSWHeaderCtrl::SetForegroundColour(const wxColour& colour)
{
if ( !wxControl::SetForegroundColour(colour) )
return false;
if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
{
customDraw->m_attr.SetTextColour(colour);
}
return true;
}
bool wxMSWHeaderCtrl::SetFont(const wxFont& font)
{
if ( !wxControl::SetFont(font) )
return false;
if ( wxMSWHeaderCtrlCustomDraw* customDraw = GetCustomDraw() )
{
customDraw->m_attr.SetFont(m_font);
}
return true;
}
// ----------------------------------------------------------------------------
// wxMSWHeaderCtrl events
// ----------------------------------------------------------------------------
wxEventType wxMSWHeaderCtrl::GetClickEventType(bool dblclk, int button)
{
wxEventType evtType;
switch ( button )
{
case 0:
evtType = dblclk ? wxEVT_HEADER_DCLICK
: wxEVT_HEADER_CLICK;
break;
case 1:
evtType = dblclk ? wxEVT_HEADER_RIGHT_DCLICK
: wxEVT_HEADER_RIGHT_CLICK;
break;
case 2:
evtType = dblclk ? wxEVT_HEADER_MIDDLE_DCLICK
: wxEVT_HEADER_MIDDLE_CLICK;
break;
default:
wxFAIL_MSG( wxS("unexpected event type") );
evtType = wxEVT_NULL;
}
return evtType;
}
bool wxMSWHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
{
NMHEADER * const nmhdr = (NMHEADER *)lParam;
wxEventType evtType = wxEVT_NULL;
int width = 0;
int order = -1;
bool veto = false;
const UINT code = nmhdr->hdr.code;
// we don't have the index for all events, e.g. not for NM_RELEASEDCAPTURE
// so only access for header control events (and yes, the direction of
// comparisons with FIRST/LAST is correct even if it seems inverted)
int idx = code <= HDN_FIRST && code > HDN_LAST ? nmhdr->iItem : -1;
if ( idx != -1 )
{
// we also get bogus HDN_BEGINDRAG with -1 index so don't call
// MSWFromNativeIdx() unconditionally for nmhdr->iItem
idx = MSWFromNativeIdx(idx);
}
switch ( code )
{
// click events
// ------------
case HDN_ITEMCLICK:
case HDN_ITEMDBLCLICK:
evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton);
// We're not dragging any more.
m_colBeingDragged = -1;
break;
// although we should get the notifications about the right clicks
// via HDN_ITEM[DBL]CLICK too according to MSDN this simply doesn't
// happen in practice on any Windows system up to 2003
case NM_RCLICK:
case NM_RDBLCLK:
{
POINT pt;
idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt);
if ( idx != wxNOT_FOUND )
{
idx = MSWFromNativeIdx(idx);
// due to a bug in mingw32 headers NM_RDBLCLK is signed
// there so we need a cast to avoid warnings about signed/
// unsigned comparison
evtType = GetClickEventType(
code == static_cast<UINT>(NM_RDBLCLK), 1);
}
//else: ignore clicks outside any column
}
break;
case HDN_DIVIDERDBLCLICK:
evtType = wxEVT_HEADER_SEPARATOR_DCLICK;
break;
// column resizing events
// ----------------------
// see comments in wxListCtrl::MSWOnNotify() for why we catch both
// ASCII and Unicode versions of this message
case HDN_BEGINTRACKA:
case HDN_BEGINTRACKW:
// non-resizable columns can't be resized no matter what, don't
// even generate any events for them
if ( !m_header.GetColumn(idx).IsResizeable() )
{
veto = true;
break;
}
m_isColBeingResized = true;
evtType = wxEVT_HEADER_BEGIN_RESIZE;
wxFALLTHROUGH;
case HDN_ENDTRACKA:
case HDN_ENDTRACKW:
width = nmhdr->pitem->cxy;
if ( evtType == wxEVT_NULL )
{
evtType = wxEVT_HEADER_END_RESIZE;
// don't generate events with invalid width
const int minWidth = m_header.GetColumn(idx).GetMinWidth();
if ( width < minWidth )
width = minWidth;
m_isColBeingResized = false;
}
break;
// The control is not supposed to send HDN_TRACK when using
// HDS_FULLDRAG (which we do use) but apparently some versions of
// comctl32.dll still do it, see #13506, so catch both messages
// just in case we are dealing with one of these buggy versions.
case HDN_TRACK:
case HDN_ITEMCHANGING:
// With "Show window contents while dragging" option enabled
// the sequence of notifications is as follows:
// HDN_BEGINTRACK
// HDN_ITEMCHANGING
// HDN_ITEMCHANGED
// ...
// HDN_ITEMCHANGING
// HDN_ITEMCHANGED
// HDN_ENDTRACK
// HDN_ITEMCHANGING
// HDN_ITEMCHANGED
// With "Show window contents while dragging" option disabled
// the sequence looks in turn like this:
// HDN_BEGINTRACK
// HDN_ITEMTRACK
// HDN_ITEMCHANGING
// ...
// HDN_ITEMTRACK
// HDN_ITEMCHANGING
// HDN_ENDTRACK
// HDN_ITEMCHANGING
// HDN_ITEMCHANGED
// In both cases last HDN_ITEMCHANGING notification is sent
// after HDN_ENDTRACK so we have to skip it.
if ( nmhdr->pitem && (nmhdr->pitem->mask & HDI_WIDTH) )
{
// prevent the column from being shrunk beneath its min width
width = nmhdr->pitem->cxy;
if ( width < m_header.GetColumn(idx).GetMinWidth() )
{
// don't generate any events and prevent the change from
// happening
veto = true;
}
// width is acceptable and notification arrived before HDN_ENDTRACK
else if ( m_isColBeingResized )
{
// generate the resizing event from here as we don't seem
// to be getting HDN_TRACK events at all, at least with
// comctl32.dll v6
evtType = wxEVT_HEADER_RESIZING;
}
// else
// Notification arriving after HDN_ENDTRACK is handled normally
// by the control but EVT_HEADER_RESIZING event cannot be generated
// because EVT_HEADER_END_RESIZE finalizing the resizing has been
// already emitted.
}
break;
// column reordering events
// ------------------------
case HDN_BEGINDRAG:
// Windows sometimes sends us events with invalid column indices
if ( nmhdr->iItem == -1 )
break;
// If we are dragging a column that is not draggable and the mouse
// is moved over a different column then we get the column number from
// the column under the mouse. This results in an unexpected behaviour
// if this column is draggable. To prevent this remember the column we
// are dragging for the complete drag and drop cycle.
if ( m_colBeingDragged == -1 )
{
m_colBeingDragged = idx;
}
// column must have the appropriate flag to be draggable
if ( !m_header.GetColumn(m_colBeingDragged).IsReorderable() )
{
veto = true;
break;
}
evtType = wxEVT_HEADER_BEGIN_REORDER;
break;
case HDN_ENDDRAG:
wxASSERT_MSG( nmhdr->pitem->mask & HDI_ORDER, "should have order" );
order = nmhdr->pitem->iOrder;
// we also get messages with invalid order when column reordering
// is cancelled (e.g. by pressing Esc)
if ( order == -1 )
break;
order = MSWFromNativeOrder(order);
evtType = wxEVT_HEADER_END_REORDER;
// We (successfully) ended dragging the column.
m_colBeingDragged = -1;
break;
case NM_RELEASEDCAPTURE:
evtType = wxEVT_HEADER_DRAGGING_CANCELLED;
// Dragging the column was cancelled.
m_colBeingDragged = -1;
break;
// other events
// ------------
case NM_CUSTOMDRAW:
if ( m_customDraw )
{
*result = m_customDraw->HandleCustomDraw(lParam);
if ( *result != CDRF_DODEFAULT )
return true;
}
break;
}
// do generate the corresponding wx event
if ( evtType != wxEVT_NULL )
{
wxHeaderCtrlEvent event(evtType, GetId());
event.SetEventObject(this);
event.SetColumn(idx);
event.SetWidth(width);
if ( order != -1 )
event.SetNewOrder(order);
const bool processed = m_header.GetEventHandler()->ProcessEvent(event);
if ( processed && !event.IsAllowed() )
veto = true;
if ( !veto )
{
// special post-processing for HDN_ENDDRAG: we need to update the
// internal column indices array if this is allowed to go ahead as
// the native control is going to reorder its columns now
if ( evtType == wxEVT_HEADER_END_REORDER )
m_header.MoveColumnInOrderArray(m_colIndices, idx, order);
if ( processed )
{
// skip default processing below
return true;
}
}
}
if ( veto )
{
// all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING
// interpret TRUE return value as meaning to stop the control
// default handling of the message
*result = TRUE;
return true;
}
return wxControl::MSWOnNotify(idCtrl, lParam, result);
}
// ============================================================================
// wxHeaderCtrl implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxHeaderCtrl construction/destruction
// ----------------------------------------------------------------------------
void wxHeaderCtrl::Init()
{
m_nativeControl = NULL;
}
bool wxHeaderCtrl::Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name)
{
long newStyle = style | wxCLIP_CHILDREN | wxTAB_TRAVERSAL;
if ( !wxWindow::Create(parent, id, pos, size, newStyle, name) )
return false;
m_nativeControl = new wxMSWHeaderCtrl(*this);
if ( !m_nativeControl->Create(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize,
wxNO_BORDER,
wxMSWHeaderCtrlNameStr) )
return false;
SetWindowStyle(newStyle);
Bind(wxEVT_SIZE, &wxHeaderCtrl::OnSize, this);
return true;
}
// ----------------------------------------------------------------------------
// wxHeaderCtrl events
// ----------------------------------------------------------------------------
void wxHeaderCtrl::OnSize(wxSizeEvent& WXUNUSED(event))
{
if (m_nativeControl != NULL) // check whether initialisation has been done
{
int cw, ch;
GetClientSize(&cw, &ch);
m_nativeControl->SetSize(0, 0, cw, ch);
}
}
// ----------------------------------------------------------------------------
// wxHeaderCtrl scrolling
// ----------------------------------------------------------------------------
void wxHeaderCtrl::DoScrollHorz(int dx)
{
m_nativeControl->ScrollHorz(dx);
}
// ----------------------------------------------------------------------------
// wxHeaderCtrl geometry calculation
// ----------------------------------------------------------------------------
wxSize wxHeaderCtrl::DoGetBestSize() const
{
return m_nativeControl->GetBestSize();
}
// ----------------------------------------------------------------------------
// wxHeaderCtrl columns managements
// ----------------------------------------------------------------------------
unsigned int wxHeaderCtrl::DoGetCount() const
{
return m_nativeControl->GetCount();
}
void wxHeaderCtrl::DoSetCount(unsigned int count)
{
m_nativeControl->SetCount(count);
}
void wxHeaderCtrl::DoUpdate(unsigned int idx)
{
m_nativeControl->UpdateHeader(idx);
}
void wxHeaderCtrl::DoSetColumnsOrder(const wxArrayInt& order)
{
m_nativeControl->SetColumnsOrder(order);
}
wxArrayInt wxHeaderCtrl::DoGetColumnsOrder() const
{
return m_nativeControl->GetColumnsOrder();
}
// ----------------------------------------------------------------------------
// wxHeaderCtrl composite window
// ----------------------------------------------------------------------------
wxWindowList wxHeaderCtrl::GetCompositeWindowParts() const
{
wxWindowList parts;
parts.push_back(m_nativeControl);
return parts;
}
void wxHeaderCtrl::SetWindowStyleFlag(long style)
{
wxHeaderCtrlBase::SetWindowStyleFlag(style);
// Update the native control style.
long flags = m_nativeControl->GetWindowStyleFlag();
if ( HasFlag(wxHD_ALLOW_REORDER) )
flags |= wxHD_ALLOW_REORDER;
else
flags &= ~wxHD_ALLOW_REORDER;
if ( HasFlag(wxHD_BITMAP_ON_RIGHT) )
flags |= wxHD_BITMAP_ON_RIGHT;
else
flags &= ~wxHD_BITMAP_ON_RIGHT;
m_nativeControl->SetWindowStyleFlag(flags);
}
#endif // wxHAS_GENERIC_HEADERCTRL
#endif // wxUSE_HEADERCTRL