关于TWinControls 的问题(200分)

  • 主题发起人 主题发起人 xiaoyu_online
  • 开始时间 开始时间
X

xiaoyu_online

Unregistered / Unconfirmed
GUEST, unregistred user!
我一直在琢磨这样一个问题,能否做这样一个控件,由TWinControls继承而来,而且可以做成
非矩形型的,如:圆形的,椭圆形的,注意是从TWinControls继承而来的,可以有焦点?
知道的兄弟请赐教。
 
加一句,要不被刷走了![:)]
 
1.可以
2.从TWinControls继承而来的,当然有焦点了,这还用问吗?
 
怎么做呢?教一下我,最好有源码
 
//给你一个创造异形窗体的例子,TForm也是TWinControl的派生类,所以道理一样

Is it possible to create forms with shapes other than the standard rectangular
shape in Windows?
Sometimes it's just not enough to write applications that have the same boring
rectangular forms over and over again. Sometimes you need a change. How about an elliptical form?
Or maybe even a triangular form? Sound intriguing? It's not that hard to do.

New in Win32 is something called a region. The Win32 API Programmer's Reference
defines a region as follows:



...a rectangle, polygon or ellipse (or a combination of two or more of these
shapes) that can be filled, painted, inverted, framed and used to perform hit testing
(testing for the cursor location).


From the definition, the most notable thing about a region is that it can be manipulated
in a variety of ways. For our purposes we want to define a region to create a specific shape.

I should point out that a region can be defined for just about any TWinControl descendant (not just forms),
meaning you can apply a region to a TPanel or even a TEdit (though I strongly recommend against it).
But to alter the shape of a TWinControl descendant, all you need to provide is a handle and employ
some handy-dandy shape change functions.

To get a control to change its shape, follow this two-step process:

Define the boundaries of the region that represent a particular shape.
Apply the boundaries you've defined to a window.
This is pretty simple. However, it's very important to refer to the help file,
and to have the source at hand. I wouldn't be able to accomplish many of my projects,
let alone write many of the articles I write here, without those two resources at my disposal.
Especially with the Windows API calls, having access to the Window.PAS file is essential
so I know what to pass into the functions. Remember, the WinAPI calls are really wrapper calls
into the appropriate Windows DLLs, and of course, the help file is essential to getting background
information on the topic you're interested in.
With respect to this article, look up the SetWindowRgn topic in Win32 Developer's Help,
and have it handy while you're putting together your program. Pay particular attention to the
Group hyperlink because it will give you a run-down of all the procedures related to the region topic.
Let's move on!

Defining a Region's Boundary
The first step to creating a form of a different shape is to define the shape itself. For our discussion,
we'll use three WinAPI calls:

//关键是下面这几个函数
CreateEllipticRgn
This function will create an elliptically-shaped region.
CreateRoundRectRgn
This will create a rectangular region with rounded corners.
CreatePolygonRgn
This will create just about any multi-sided shape, as long as the lines form a closed solid.

These functions return a HRGN type, which will then be used by a function called SetWindowRgn
whose sole purpose in life it is to set the parameters defined by a particular region variable.
I've encapsulated these functions in methods that are part of a demonstration form.
The functions are coded as follows:
{===========================================================================
Notice that all the functions are used in an assignment
operation to a variable called rgn. This is a
private var that I declared for the form. The private var is
accessible to all functions; I did this so that I could change the shape of
the form or a control on the form, and use the same region.
===========================================================================}

procedure TForm1.DrawEllipticRegion(wnd : HWND; rect : TRect);
begin
rgn := CreateEllipticRgn(rect.left, rect.top, rect.right, rect.bottom);
SetWindowRgn(wnd, rgn, TRUE);
end;

procedure TForm1.DrawRndRectRegion(wnd : HWND; rect : TRect);
begin
rgn := CreateRoundRectRgn(rect.left, rect.top, rect.right, rect.bottom, 30, 30);
SetWindowRgn(wnd, rgn, TRUE);
end;

procedure TForm1.DrawPolygonRegion(wnd : HWND; rect : TRect; NumPoints : Integer; DoStarShape : Boolean);
const
RadConvert = PI/180;
Degrees = 360;
MaxLines = 100;
var
x, y,
xCenter,
yCenter,
radius,
pts,
I : Integer;
angle,
rotation: Extended;
arPts : Array[0..MaxLines] of TPoint;
begin

xCenter := (rect.Right - rect.Left) div 2;
yCenter := (rect.Bottom - rect.Top) div 2;
if DoStarShape then
begin
rotation := Degrees/(2*NumPoints);
pts := 2 * NumPoints;
end
else
begin
rotation := Degrees/NumPoints;
//get number of degrees to turn per point
pts := NumPoints
end;
radius := yCenter;

{This loop defines the Cartesian points of the shape.
Notice I've added 90 degrees to the rotation angle.
This is so that shapes will stand up; otherwise they'll lie on their sides.
I had to brush up on my trigonometry to accomplish this
(forgot all those sin and cos thingies).
Many thanks to Terry Smithwick and David Ullrich for their assistance on CompuServe!}
for I := 0 to pts - 1 do begin
if DoStarShape then
if (I mod 2) = 0 then //which means that
radius := Round(radius/2)
else
radius := yCenter;

angle := ((I * rotation) + 90) * RadConvert;
x := xCenter + Round(cos(angle) * radius);
y := yCenter - Round(sin(angle) * radius);
arPts.X := x;
arPts.Y := y;
end;

rgn := CreatePolygonRgn(arPts, pts, WINDING);
SetWindowRgn(wnd, rgn, TRUE);
end;

The first two functions are pretty simple, just two-liners.
All that's needed to create the appropriate shapes is a handle and
a TRect structure. For forms, that structure would be taken from
the ClientRect property; for other controls, use the BoundsRect property.

The DrawPolygonRegion method, however, is much more complex.
This is due in part to the fact that CreatePolygonRgn requires
the vertices of the corners of the polygon to be passed as an array of TPoints,
and partly because I wanted to draw equilateral polygons based off points
rotated around a common center point. For that I had to use some trigonometry.

I wanted to not only draw polygon regions, but stars as well. Using rotational
trig allowed me to do it. The way the function works if the DrawStarShape parameter
is set to True is that for every even value of I in the loop, the radius of the circle is
set to half its length, and to maintain the number of points of the polygon I want to draw,
I double the number of points to accomodate the contraction of the radius.

At the very end of each function is a call to SetWindowRgn. This function takes as parameters
a window handle, a rgn var, and a Boolean value that specifies whether the window should be re-drawn.
In all cases, if you want to see the shape you've made, this must be always be set to True.

Below is the listing for the entire source code of my test form. On the form I've dropped four
TButtons (one for each of the shapes: ellipse, round rectangle, polygon and star);
a TPanel to demonstrate the ability to set regions for TWinControl descendants other than TForm;
and a SpinEdit used in conjunction with the Polygon and Star region buttons to define the number of points
that'll be defining the shape. Here's the code:
unit regmain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, Spin;

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
SpinEdit1: TSpinEdit;
Button4: TButton;
Panel1: TPanel;
Edit1: TEdit;
procedure DrawRndRectRegion(wnd : HWND; rect : TRect);
procedure DrawEllipticRegion(wnd : HWND; rect : TRect);
procedure DrawPolygonRegion(wnd : HWND; rect : TRect; NumPoints : Integer; DoStarShape : Boolean);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
rgn : HRGN;
rect : TRect;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.DrawRndRectRegion(wnd : HWND; rect : TRect);
begin
rgn := CreateRoundRectRgn(rect.left, rect.top, rect.right, rect.bottom, 30, 30);
SetWindowRgn(wnd, rgn, TRUE);
end;

procedure TForm1.DrawEllipticRegion(wnd : HWND; rect : TRect);
begin
rgn := CreateEllipticRgn(rect.left, rect.top, rect.right, rect.bottom);
SetWindowRgn(wnd, rgn, TRUE);
end;

procedure TForm1.DrawPolygonRegion(wnd : HWND; rect : TRect; NumPoints : Integer; DoStarShape : Boolean);
const
RadConvert = PI/180;
Degrees = 360;
MaxLines = 100;
var
x, y,
xCenter,
yCenter,
radius,
pts,
I : Integer;
angle,
rotation: Extended;
arPts : Array[0..MaxLines] of TPoint;
begin

xCenter := (rect.Right - rect.Left) div 2;
yCenter := (rect.Bottom - rect.Top) div 2;
if DoStarShape then
begin
rotation := Degrees/(2*NumPoints);
pts := 2 * NumPoints;
end
else
begin
rotation := Degrees/NumPoints; //get number of degrees to turn per point
pts := NumPoints
end;
radius := yCenter;

{This loop defines the Cartesian points of the shape. Again,
I've added 90 degrees to the rotation angle so the shapes will
stand up rather than lie on their sides.
Thanks again to Terry Smithwick and David Ullrich for their trig help on CompuServe.}
for I := 0 to pts - 1 do begin
if DoStarShape then
if (I mod 2) = 0 then //which means that
radius := Round(radius/2)
else
radius := yCenter;

angle := ((I * rotation) + 90) * RadConvert;
x := xCenter + Round(cos(angle) * radius);
y := yCenter - Round(sin(angle) * radius);
arPts.X := x;
arPts.Y := y;
end;

rgn := CreatePolygonRgn(arPts, pts, WINDING);
SetWindowRgn(wnd, rgn, TRUE);
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
DrawEllipticRegion(Form1.Handle, Form1.ClientRect);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
DrawPolygonRegion(Panel1.Handle, Panel1.BoundsRect, SpinEdit1.Value, False);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
DrawRndRectRegion(Form1.Handle, Form1.ClientRect);
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
DrawPolygonRegion(Panel1.Handle, Panel1.BoundsRect, SpinEdit1.Value, True);
end;

end.

As you can see, defining and setting regions is pretty easy.
Look in the help file for in-depth discussions.
If you belong to the MS Developer's Network,
the library CDs discuss this topic comprehensively.
 
SetWindowRgn()只是对Form有效,但对于其他的控件不太有效,不知是我对控件了解的不够透彻
还是别的,我也试过上面的方法,但没有成功,我希望看见这样一个控件就好了!
 
还有没有更好的建议!!
 
SetWindowRgn不是对Form有效!是对窗口有效!!
windows认为窗口控件(TWinControl)就是一个窗口。
 
一个无用的椭圆 Panel:
unit Panel1;
interface
uses
Windows, Messages, Classes, Controls, ExtCtrls, Graphics;
type
TPanel1 = class(TPanel)
private
FHRgn: HRGN;
procedure WMSize(var Msg: TWMSize); message WM_SIZE;
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
procedure Paint; override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TPanel1]);
end;

{ TPanel1 }

procedure TPanel1.CreateWnd;
begin
inherited;
FHRgn := CreateEllipticRgn(ClientRect.Left, ClientRect.Top, ClientRect.Right, ClientRect.Bottom);
SetWindowRgn(Handle, FHRgn, True);
end;

procedure TPanel1.DestroyWnd;
begin
inherited;
DeleteObject(FHRgn);
end;

procedure TPanel1.Paint;
var
SavedBrushStyle : TBrushStyle;
SavedBrushColor: TColor;
begin
SavedBrushStyle := Canvas.Brush.Style;
SavedBrushColor := Canvas.Brush.Color;
try
Canvas.Brush.Style := bsCross;
Canvas.Brush.Color := clGreen;
Canvas.Ellipse(ClientRect);
finally
Canvas.Brush.Style := SavedBrushStyle;
Canvas.Brush.Color := SavedBrushColor;
end;
end;

procedure TPanel1.WMSize(var Msg: TWMSize);
begin
inherited;
DeleteObject(FHRgn);
FHRgn := CreateEllipticRgn(ClientRect.Left, ClientRect.Top, ClientRect.Right, ClientRect.Bottom);
SetWindowRgn(Handle, FHRgn, True);
end;
end.
 
窗口类泛指具有句柄、可获取焦点的类。vcl.vclxx.org上面大把的的非矩形按键源码下载。
 
有时候我们可以用非正统的方法解决问题,为什么一定要用SetWindowRgn来更改TWinControl
我们为什么不能用矩形来表达非矩形的东西?其实我们可以Override掉TWinControl与焦点
相关的过程或函数,比如MouseDown、MouseUp等过程,我们可以通过判断鼠标的位置是否
在我们希望的区域来决定是否激发相应的事件,这样看起来不就达到希望的效果了吗?
有时候不一定非要用看起来高深的方法来解决看起来高深的问题。
 
多人接受答案了。
 
后退
顶部