您的位置:首页 > Web前端

[Effective WX] 理解wxWindow中的各种size

2012-08-16 22:31 134 查看
在wxWidget中,wxWindow有相当多的size,每种size有各种不同的用途,特别是与wxSize联系起来时,就特别容易混淆。这里从代码的角度来理清各种size。

那几种size就是size, min size, max size, best size, virtual size

首先我们来看wxWindowBase类中几个size的数据成员:
min size/max size相关:
// the minimal allowed size for the window (no minimal size if variable(s)
// contain(s) wxDefaultCoord)
int                  m_minWidth,
m_minHeight,
m_maxWidth,
m_maxHeight;


[align=left]上面几个成员变量是关于min size和max size的width/height.[/align]

[align=left]virtual size 相关:[/align]
// Virtual size (scrolling)
wxSize                m_virtualSize;
int                   m_minVirtualWidth;    // VirtualSizeHints
int                   m_minVirtualHeight;
int                   m_maxVirtualWidth;
int                   m_maxVirtualHeight;


[align=left]best size相关:[/align]

// Used to save the results of DoGetBestSize so it doesn't need to be
// recalculated each time the value is needed.
wxSize m_bestSizeCache;


这个变量是关于best size的。通常best size都是计算出来(后面将会看到),不过有些窗口控件,可以将这个size cache下来,不用重复计算。

[align=left]size相关:[/align]
[align=left]wxWindowBase并没有维护一个关于size的成员,GetSize/SetSize都是直接调用api来获取的。[/align]

[align=left]我们客户端代码调用窗口的SetSize函数就是调用DoSetSize函数。[/align]

// set the window size and/or position
void SetSize( int x, int y , int width, int height,
int sizeFlags = wxSIZE_AUTO )
{  DoSetSize(x , y, width, height , sizeFlags); }

void SetSize( int width, int height )
{ DoSetSize( wxDefaultCoord , wxDefaultCoord, width, height , wxSIZE_USE_EXISTING ); }

void SetSize( const wxSize& size )
{ SetSize( size .x, size.y ); }

void SetSize(const wxRect& rect, int sizeFlags = wxSIZE_AUTO)
{ DoSetSize(rect .x, rect.y , rect. width, rect .height, sizeFlags); }

[align=left]实际上DoSetSize函数在wxWindowBase中是一个纯虚函数,由任何继承的实际窗口类来重写。[/align]
[align=left]在windows平台上,wxWindowMSW来实现了这个DoSetSize函数:[/align]

// set the size of the window: if the dimensions are positive, just use them,
// but if any of them is equal to -1, it means that we must find the value for
// it ourselves (unless sizeFlags contains wxSIZE_ALLOW_MINUS_ONE flag, in
// which case -1 is a valid value for x and y)
//
// If sizeFlags contains wxSIZE_AUTO_WIDTH/HEIGHT flags (default), we calculate
// the width/height to best suit our contents, otherwise we reuse the current
// width/height
void wxWindowMSW ::DoSetSize( int x , int y, int width, int height , int sizeFlags)
{
// get the current size and position...
int currentX, currentY ;
int currentW, currentH ;

GetPosition(& currentX, ¤tY );
GetSize(& currentW, ¤tH );

// ... and don't do anything (avoiding flicker) if it's already ok unless
// we're forced to resize the window
if ( x == currentX && y == currentY &&
width == currentW && height == currentH &&
!( sizeFlags & wxSIZE_FORCE ) )
{
return;
}

if ( x == wxDefaultCoord && !(sizeFlags & wxSIZE_ALLOW_MINUS_ONE) )
x = currentX ;
if ( y == wxDefaultCoord && !(sizeFlags & wxSIZE_ALLOW_MINUS_ONE) )
y = currentY ;

AdjustForParentClientOrigin( x, y , sizeFlags);

wxSize size = wxDefaultSize ;
if ( width == wxDefaultCoord )
{
if ( sizeFlags & wxSIZE_AUTO_WIDTH )
{
size = DoGetBestSize ();
width = size .x;
}
else
{
// just take the current one
width = currentW ;
}
}

if ( height == wxDefaultCoord )
{
if ( sizeFlags & wxSIZE_AUTO_HEIGHT )
{
if ( size .x == wxDefaultCoord )
{
size = DoGetBestSize ();
}
//else: already called DoGetBestSize() above

height = size .y;
}
else
{
// just take the current one
height = currentH ;
}
}

DoMoveWindow( x, y , width, height);
}


可以看到,通常我们调用SetSize都是传入参数wxSIZE_USE_EXISTING,加入我们传入wxDefaultSize,我们不会用DoGetBestSize 函数返回的结果,而只是用currentW/currentH的值。也就是调用GetSize(& currentW ,
¤tH );得到的值。正常情况,通过API来设置改变窗口的大小。

[align=left]有趣的是,这个函数相似地调用DoGetSize函数,也是一个纯虚函数,在wxWindowBase类中。wxWindowMSW重新实现了它。[/align]
[align=left]下面函数中的wxGetWindowRect就是得到整个窗口的size(包括caption, client region, 和border等)。[/align]

// Get total size
void wxWindowMSW ::DoGetSize( int *x , int * y) const
{
{
RECT rect = wxGetWindowRect( GetHwnd());

if ( x )
* x = rect .right - rect.left ;
if ( y )
* y = rect .bottom - rect.top ;
}
}


[align=left]wxWindow创建过程来跟踪各个size的处理[/align]

[align=left]我们通常创建一个wxWindow时,传入Create或者构造函数中size,可以是wxDefaultSize也可以是一个具体值,这个值将会传入wxWindowMSW中的wxWindowMSW::Create函数,我们来看下这个函数干了些什么事情:[/align]

bool wxWindowMSW ::Create( wxWindow *parent ,
wxWindowID id ,
const wxPoint & pos,
const wxSize & size,
long style ,
const wxString & name)
{
wxCHECK_MSG( parent, false , wxT( "can't create wxWindow without parent") );

if ( ! CreateBase(parent , id, pos, size , style, wxDefaultValidator, name ) )
return false ;

parent-> AddChild(this );

WXDWORD exstyle;
DWORD msflags = MSWGetCreateWindowFlags (&exstyle);

#ifdef __WXUNIVERSAL__
// no borders, we draw them ourselves
exstyle &= ~(WS_EX_DLGMODALFRAME |
WS_EX_STATICEDGE |
WS_EX_CLIENTEDGE |
WS_EX_WINDOWEDGE);
msflags &= ~WS_BORDER;
#endif // wxUniversal

if ( IsShown() )
{
msflags |= WS_VISIBLE ;
}

if ( ! MSWCreate(wxCanvasClassName , NULL, pos, size , msflags, exstyle) )
return false ;

InheritAttributes();

return true;
}


[align=left]我们关心的2个函数调用就是CreateBaseMSWCreate,前者是基类wxWindowBase的函数,但是这个函数几乎没干什么事情,直接将size忽略掉了。用size参数的是MSWCreate函数。这是个跟windows平台相关的函数。这个函数调用了(void) MSWGetCreateWindowCoords(pos , size, x, y , w, h);。将size传了进去。返回的x/y/w/h,为具体的position和size信息。[/align]

if ( size.x == wxDefaultCoord || size.y == wxDefaultCoord)
{
nonDefault = true ;
}
w = WidthDefault(size .x);
h = HeightDefault(size .y);

[align=left]WidthDefault/HeightDefault几乎没干什么事情,如果传入值是-1,就直接返回一个固定值为20,否则,原封不动的返回传入的值。所以说,我们通常创建一个窗口时,如果不指定size,那么它就是默认的size (20, 20)。至少从这里看到是这样的。[/align]

[align=left]我们再以wxButton的创建过程来跟踪代码:[/align]
[align=left]wxButton* pBtn = new wxButton(parentWin, wxID_ANY, "button", wxDefaultPosition, wxDefaultSize);[/align]

wxButton构造函数会调用Create函数,传入相应的参数。Create函数会调用父类wxControl的CreateControl函数

if ( ! CreateControl (parent , id, pos , size , style, validator , name ) )
return false ;

[align=left]这个CreateControl几乎不干实际的活,简单的调用wxWindowBase类的CreateBase函数。前面说过CreateBase不看size参数。[/align]
[align=left]所以实际干活的是这一句:[/align]

[align=left]
return MSWCreateControl ( _T( "BUTTON" ), msStyle , pos, size , label , exstyle );
[/align]
[align=left]这个是基类wxControl的函数,做了一些关于size的事情如下:[/align]

// choose the position for the control: we have a problem with default size
// here as we can't calculate the best size before the control exists
// (DoGetBestSize() may need to use m_hWnd), so just choose the minimal
// possible but non 0 size because 0 window width/height result in problems
// elsewhere
int x = pos . x == wxDefaultCoord ? 0 : pos . x,
y = pos . y == wxDefaultCoord ? 0 : pos . y,
w = size . x == wxDefaultCoord ? 1 : size . x,
h = size . y == wxDefaultCoord ? 1 : size . y;
...
SetInitialSize(size);


[align=left]从这里看出,它仅仅是给wxButton一个(1, 1)的最小size。注意,从comment可以看到,此时无法拿到best size,因为还没有创建button control。[/align]
[align=left]然后重要的wxWindowBase类的SetInitialSize函数调用。[/align]
void wxWindowBase :: SetInitialSize( const wxSize & size)
{
// Set the min size to the size passed in.  This will usually either be
// wxDefaultSize or the size passed to this window's ctor/Create function.
SetMinSize( size );

// Merge the size with the best size if needed
wxSize best = GetEffectiveMinSize ();

// If the current size doesn't match then change it
if ( GetSize () != best )
SetSize (best );
}


// Call these to override what GetBestSize() returns. This
// method is only virtual because it is overriden in wxTLW
// as a different API for SetSizeHints().
virtual void SetMinSize ( const wxSize & minSize ) { m_minWidth = minSize .x ; m_minHeight = minSize .y ; }
virtual void SetMaxSize ( const wxSize & maxSize ) { m_maxWidth = maxSize .x ; m_maxHeight = maxSize .y ; }

// Override these methods to impose restrictions on min/max size.
// The easier way is to call SetMinSize() and SetMaxSize() which
// will have the same effect. Doing both is non-sense.
virtual wxSize GetMinSize () const { return wxSize ( m_minWidth, m_minHeight ); }
virtual wxSize GetMaxSize () const { return wxSize ( m_maxWidth, m_maxHeight ); }


[align=left]注意到SetInitialSize函数内部的注释。首先调用SetMinSize函数,如果用户没有传入Size,比如wxDefaultSize,那么m_minWidth/m_minHeight就会为-1。如果有那么minSize就等于create时传入的值,除非后面再调用SetMinSize进行了更改。[/align]
[align=left]调用GetEffectiveMinSize获取一个best size。之后与GetSize()函数返回的size做比较,不相等,则SetSize到best size。[/align]
[align=left]一旦创建wxButton之后,调用GetSize(调用DoGetSize),是通过hwnd拿到窗口的size。涉及到win32 api。[/align]

[align=left]我们来看下GetEffectiveMinSize做了什么事情:[/align]

wxSize wxWindowBase :: GetEffectiveMinSize() const
{
// merge the best size with the min size, giving priority to the min size
wxSize min = GetMinSize ();
if ( min .x == wxDefaultCoord || min .y == wxDefaultCoord)
{
wxSize best = GetBestSize();
if (min . x == wxDefaultCoord ) min . x =  best .x ;
if (min . y == wxDefaultCoord ) min . y =  best .y ;
}
return min ;
}


[align=left]首先获取minSize,如果有宽度或高度为-1,则要获取best size,因为那个min size就是假的。用best size来更新这个min size。继续看GetBestSize函数:[/align]

// get the size best suited for the window (in fact, minimal
// acceptable size using which it will still look "nice" in
// most situations)
wxSize GetBestSize () const
{
if (m_bestSizeCache . IsFullySpecified())
return m_bestSizeCache ;
return DoGetBestSize ();
}


[align=left]如果我们之前设置了best size cache,那么就会直接返回这个size。否则就需要调用虚函数DoGetBestSize来获取具体控件/窗口的best size。[/align]

// get the size which best suits the window: for a control, it would be
// the minimal size which doesn't truncate the control, for a panel - the
// same size as it would have after a call to Fit()
virtual wxSize DoGetBestSize () const;


[align=left]注意这个函数是虚函数,并不是纯虚的,所以wxWindowBase提供类个默认实现。我们先来看它的实现:[/align]

// return the size best suited for the current window
wxSize wxWindowBase :: DoGetBestSize() const
{
wxSize best ;

if ( m_windowSizer )
{
// Adjust to window size, since the return value of GetWindowSizeForVirtualSize is
// expressed in window and not client size
wxSize minSize = m_windowSizer-> GetMinSize ();
wxSize size ( GetSize());
wxSize clientSize ( GetClientSize());

wxSize minWindowSize ( minSize. x + size . x - clientSize .x ,
minSize .y + size. y - clientSize . y);

best = GetWindowSizeForVirtualSize ( minWindowSize);

return best ;
}
else if ( !GetChildren (). empty()
#ifdef __WXMAC__
&& wxHasRealChildren(this)
#endif
)
{
// our minimal acceptable size is such that all our visible child
// windows fit inside
int maxX = 0,
maxY = 0;

for ( wxWindowList :: compatibility_iterator node = GetChildren ().GetFirst ();
node ;
node = node -> GetNext() )
{
wxWindow *win = node-> GetData ();
if ( win -> IsTopLevel()
|| ! win ->IsShown ()
#if wxUSE_STATUSBAR
|| wxDynamicCast (win , wxStatusBar)
#endif // wxUSE_STATUSBAR
)
{
// dialogs and frames lie in different top level windows -
// don't deal with them here; as for the status bars, they
// don't lie in the client area at all
continue ;
}

int wx , wy, ww , wh ;
win ->GetPosition (& wx, & wy );

// if the window hadn't been positioned yet, assume that it is in
// the origin
if ( wx == wxDefaultCoord )
wx = 0;
if ( wy == wxDefaultCoord )
wy = 0;

win ->GetSize (& ww, & wh );
if ( wx + ww > maxX )
maxX = wx + ww;
if ( wy + wh > maxY )
maxY = wy + wh;
}

best = wxSize ( maxX, maxY );
}
else // ! has children
{
// for a generic window there is no natural best size so, if the
// minimal size is not set, use the current size but take care to
// remember it as minimal size for the next time because our best size
// should be constant: otherwise we could get into a situation when the
// window is initially at some size, then expanded to a larger size and
// then, when the containing window is shrunk back (because our initial
// best size had been used for computing the parent min size), we can't
// be shrunk back any more because our best size is now bigger
wxSize size = GetMinSize();
if ( !size . IsFullySpecified() )
{
size .SetDefaults ( GetSize());
wxConstCast (this , wxWindowBase)-> SetMinSize (size );
}

// return as-is, unadjusted by the client size difference.
return size ;
}

// Add any difference between size and client size
wxSize diff = GetSize () - GetClientSize();
best. x += wxMax (0, diff. x );
best. y += wxMax (0, diff. y );

return best ;
}


[align=left]函数针对不同情况,有不同的处理。[/align]
[align=left]a) 假设窗口有一个sizer,那么获取sizer的minSize。也就是能容纳sizer里面所有子窗口的最小的size,然后加上non-client的sizer,就是窗口期望的best size,因为这样我们就能看到它所含的sizer里面的所有的子窗口。[/align]

// Adjust to window size, since the return value of GetWindowSizeForVirtualSize is
// expressed in window and not client size
wxSize minSize = m_windowSizer-> GetMinSize ();
wxSize size ( GetSize());
wxSize clientSize ( GetClientSize());

wxSize minWindowSize ( minSize. x + size . x - clientSize .x ,
minSize .y + size. y - clientSize . y);

best = GetWindowSizeForVirtualSize ( minWindowSize);

return best ;


GetWindowSizeForVirtualSize函数默认返回传入的参数,对于非scroll window类的窗口来说,都是如此。因为non-scroll window没有virtual size的说法。通常来说,best virtual size就是能够容纳所有的子窗口的size,但是一般client 窗口无法显示这么大的区域,在scroll
window类的窗口中。所以在scroll window类的窗口中,best size就是best virtual size。

[align=left]b) 考虑自身没有sizer,但是有子窗口的情况:[/align]

win ->GetSize (& ww, & wh );
if ( wx + ww > maxX )
maxX = wx + ww;
if ( wy + wh > maxY )
maxY = wy + wh;
}

best = wxSize ( maxX, maxY );


[align=left]loop所有的子窗口,计算它们的size,overlap所有的窗口矩形,求解最小的fitting window size。[/align]
[align=left]针对这种情况,还需要加上non-client 的size.[/align]

// Add any difference between size and client size
wxSize diff = GetSize () - GetClientSize();
best. x += wxMax (0, diff. x );
best. y += wxMax (0, diff. y );


[align=left]c) 最后一种情况,就是没有子窗口,是一个树叶窗口节点。 [/align]

// for a generic window there is no natural best size so, if the
// minimal size is not set, use the current size but take care to
// remember it as minimal size for the next time because our best size
// should be constant: otherwise we could get into a situation when the
// window is initially at some size, then expanded to a larger size and
// then, when the containing window is shrunk back (because our initial
// best size had been used for computing the parent min size), we can't
// be shrunk back any more because our best size is now bigger
wxSize size = GetMinSize();
if ( !size . IsFullySpecified() )
{
size .SetDefaults ( GetSize());
wxConstCast (this , wxWindowBase)-> SetMinSize (size );
}

// return as-is, unadjusted by the client size difference.
return size ;


[align=left]首先它获取minsize,如果是个有效的size(width != -1 && height != -1),那么就直接返回它。否则就用GetSize返回的结果。结合上面的GetEffectiveSize函数调用过程,如果我们没有传入size,那么GetEffectivSize函数返回的结果就是窗口创建后给出的size。注意,comment说到,会重新设置minsize到这个最好的size。[/align]

[align=left]针对wxButton,它重新实现了DoGetBestSize函数:[/align]

wxSize wxButton :: DoGetBestSize() const
{
wxClientDC dc (wx_const_cast ( wxButton *, this ));
dc. SetFont (GetFont ());

wxCoord wBtn ,
hBtn ;
dc. GetMultiLineTextExtent (GetLabelText (), & wBtn, & hBtn );

// add a margin -- the button is wider than just its label
wBtn += 3* GetCharWidth ();
hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT (hBtn );

// all buttons have at least the standard size unless the user explicitly
// wants them to be of smaller size and used wxBU_EXACTFIT style when
// creating the button
if ( ! HasFlag (wxBU_EXACTFIT ) )
{
wxSize sz = GetDefaultSize();
if (wBtn > sz. x )
sz .x = wBtn;
if (hBtn > sz. y )
sz .y = hBtn;

return sz ;
}

wxSize best (wBtn , hBtn);
CacheBestSize( best );
return best ;
}


主要是根据button caption字体进行了计算。GetDefaultSize来做实际的活,主要是一个经验公式。
wxSize wxButtonBase::GetDefaultSize()
{
static wxSize s_sizeBtn;

if ( s_sizeBtn.x == 0 )
{
wxScreenDC dc;
dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));

// the size of a standard button in the dialog units is 50x14,
// translate this to pixels
// NB1: the multipliers come from the Windows convention
// NB2: the extra +1/+2 were needed to get the size be the same as the
//      size of the buttons in the standard dialog - I don't know how
//      this happens, but on my system this size is 75x23 in pixels and
//      23*8 isn't even divisible by 14... Would be nice to understand
//      why these constants are needed though!
s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
}

return s_sizeBtn;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: