@会网络的老鼠

涂飞平的博客空间

delphi组件Align实现方式及应用 [原]

12 年前 0

很多时候我们做的组件是一种包容组件,里面可以包含其他组件,其实做包容组件是很简单的,因为windows本身就支持包容组件的创建,只要在createwindowex中设置了扩展属性:WS_EX_CONTROLPARENT,然后设置属性(Style)为:WS_CLIPCHILDREN就可以了,前一个参数表示这个窗口可以放置其他的窗口(即:可以作为父控件来使用),第二个参数表示的在绘制控件的时候会前剪切子控件的范围,然后再画子控件!有了这个支持,我们可以很容易做出容纳别的组件的组件。
delphi对于这种组件是从TWinControl开始支持的(毕竟TControl没有Handle属性,还不是窗口,所以也没有必要支持了!),下面看看这部分的代码:

procedure TWinControl.CreateParams(var Params: TCreateParams);
begin
FillChar(Params, SizeOf(Params), 0);
with Params do
begin
Caption := FText;
Style := WS_CHILD or WS_CLIPSIBLINGS;
AddBiDiModeExStyle(ExStyle);
[b]if csAcceptsControls in ControlStyle then[/b]
begin
Style := Style or WS_CLIPCHILDREN;
[color=red]ExStyle := ExStyle or WS_EX_CONTROLPARENT;[/color]
end;
if not (csDesigning in ComponentState) and not Enabled then
Style := Style or WS_DISABLED;
if FTabStop then Style := Style or WS_TABSTOP;
X := FLeft;
Y := FTop;
Width := FWidth;
Height := FHeight;
if Parent nil then
WndParent := Parent.GetHandle else
WndParent := FParentWindow;
WindowClass.style := CS_VREDRAW + CS_HREDRAW + CS_DBLCLKS;
WindowClass.lpfnWndProc := @DefWindowProc;
WindowClass.hCursor := LoadCursor(0, IDC_ARROW);
WindowClass.hbrBackground := 0;
WindowClass.hInstance := HInstance;
StrPCopy(WinClassName, ClassName);
end;
end;
看了代码,我们知道在delphi只要简单设置ControlStyle属性为csAcceptsControls就可以接受其他组件了,那我们如果想要去处这个特性的话,可以这样:
ControlStyle:=ControlStyle-[csAcceptsControls];
[b]注:
对于集合的操作,一般可以使用Include和Exclude来操作,但操作的部分必须是变量,但是这里的ControlStyle是一个属性,虽然映射到变量,但本身并非变量,所以不能使用那两个操作函数,而应该使用+/-操作符![/b]
下面说Align了,子组件的Align是对父组件而言的,毕竟它的对齐方式是指在父窗口范围的显示方式,所以它的显示范围是由父窗口来规定的,一般的情况下,如果Align为alClient的话,那么它会得到父窗口的ClientRect范围并将自己放大到一样大,这样就可以达到充满显示的效果了,但很多时候我们希望子组件采用alClient方式充满(这样有个好处,它的位置是认为调不了的,而是根据父组件的大小而自动调整的),但并不要它占满全部的父组件范围,而是占一部分,很多组件都有这种要求,例如PageControl,在PageControl中,所有子页的填充方式都是alClient方式,但父组件必须留点空间以显示切换按钮,类似要求在很多情况都会出现,那怎么办呢?下面是delphi中的解决方案(代码太多,有删节):
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
var
AlignList: TList;
......
begin
......
if AlignWork then
begin
[color=red]AdjustClientRect(Rect);[/color]//这里是要计算的Rect了
AlignList := TList.Create;
try
DoAlign(alTop);
DoAlign(alBottom);
DoAlign(alLeft);
DoAlign(alRight);
DoAlign(alClient);
DoAlign(alCustom);
DoAlign(alNone); // Move anchored controls
ControlsAligned;
finally
AlignList.Free;
end;
end;
{ Apply any constraints }
if Showing then AdjustSize;
end;
再来看看核心的函数:
procedure TWinControl.AdjustClientRect(var Rect: TRect);
begin
{ WM_NCCALCSIZE performs our BorderWidth logic }
end;
它是个空函数,什么也没有实现,并且它是个Virtual方法,后代类可以改写!
它的参数是个var Rect,引用类型的,说明它是可以更改的,而AlignControls函数中的子函数如:DoAlign(alBottom);调用的范围就是这个函数返回的Rect,如果AdjustClientRect这个函数什么都没有做的话,那么Rect参数是不改变的,还是父窗口的ClientRect(即:默认情况下alClient的确是填充父组件的全部范围):
procedure TWinControl.AlignControl(AControl: TControl);
var
Rect: TRect;
begin
if not HandleAllocated or (csDestroying in ComponentState) then Exit;
if FAlignLevel 0 then
Include(FControlState, csAlignmentNeeded)
else
begin
DisableAlign;
try
Rect := GetClientRect;//这里给Rect一个初始值
AlignControls(AControl, Rect);//传给函数
finally
Exclude(FControlState, csAlignmentNeeded);
EnableAlign;
end;
end;
end;
现在我们知道了来龙去脉,只要改写AdjustClientRect就可以达到效果了,很简单吧!?
下面是TPageControl类的改写函数:
procedure TCustomTabControl.AdjustClientRect(var Rect: TRect);
begin
Rect := DisplayRect;
inherited AdjustClientRect(Rect);
end;
这里它就改写了传进来的Rect了,不再是ClientRect了,而是DisplayRect了!!!!
而我做了一个测试的类,核心代码如下:
procedure TSundyArea.AdjustClientRect(var Rect: TRect);
begin
Rect.Top := 20;
Rect.Left := 0;
Rect.Bottom := Height;
Rect.Right := Width;
inherited AdjustClientRect(Rect);
end;
可以很好的达到我们要的效果了!

编写评论