0

I've been experimenting to see if I can get the same effect with a custom control with no luck.

The issue is, I'm wanting to make a resizable panel like component derived from Tcustomcontrol.

I can create a single pixel border with WS_BORDER and then use WMNCHitTest to detect the edges. But if the control contains another control aligned to alclient, then the mouse messages go to that contained component rather than the containing panel. So at best, the resizing cursors only work when they are precisely over the single pixel border.

Changing to WS_THICKFRAME obviously works but makes an ugly visible border.

I noticed that WIN10 forms have an invisible thick border with just a single pixel line on the inner edges. So the resizing cursors work outside the visible frame for about 6 to 8 pixels making it much easier to select.

Any ideas on how they are achieving that effect and can it be easily duplicated in delphi vcl controls?

Andy k
  • 1,056
  • 1
  • 11
  • 22
  • @SertacAkyuz: Isn't the OP talking about a child control on a form? – Andreas Rejbrand Feb 11 '19 at 21:38
  • @AndreasRejbrand: Yes,the wiindows 10 form borders are different to previous versions and you can't duplicate it with the Createparams style parameters. Obviously if you make a new vcl form, you will get a Win10 form. The challenge is to duplicate the style on a custom component. – Andy k Feb 11 '19 at 21:43
  • @Andreas - Maybe I got confused by resizable borders.. – Sertac Akyuz Feb 11 '19 at 21:49
  • So why don't you just handle WM_NCCALCSIZE and then WM_NCHITTEST? ... Perhaps I still don't understand though... – Sertac Akyuz Feb 11 '19 at 22:01
  • @SertacAkyuz: I'm not sure how to do that. I'm wondering if its simpler to just go borderless and create a transparent region inside the controls edges, then draw the border manually. – Andy k Feb 11 '19 at 22:40
  • @Andy - What happens to alClient children then? – Sertac Akyuz Feb 11 '19 at 22:52
  • @Sertac I guess I would have to shrink the client area somehow. I'm not sure how to do that and still draw outside the client area to make the border. I have been doing more searching and found this which I will try out. https://stackoverflow.com/questions/26514357/how-to-draw-a-custom-border-inside-the-non-client-area-of-a-control-with-scroll – Andy k Feb 12 '19 at 07:58

1 Answers1

2

You don't need borders that are meant to be used with top-level windows, handle WM_NCCALCSIZE to deflate your client area:

procedure TSomeControl.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
  inherited;
  InflateRect(Message.CalcSize_Params.rgrc0, -FBorderWidth, -FBorderWidth);
end;

where FBorderWidth is the supposed padding around the control.

Handle WM_NCHITTEST to resize with the mouse from borders.

procedure TSomeControl.WMNCHitTest(var Message: TWMNCHitTest);
var
  Pt: TPoint;
begin
  inherited;
  Pt := ScreenToClient(Point(Message.XPos, Message.YPos));
  if Pt.X < 0 then
    Message.Result := HTLEFT;
  ...

Of course you have to paint the borders to your liking.


Here's my full test unit:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  extctrls;

type
  TSomeControl = class(TCustomControl)
  private
    FBorderWidth: Integer;
  protected
    procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
  public
    constructor Create(AOwner: TComponent); override;
  end;

{ TSomeControl }

constructor TSomeControl.Create(AOwner: TComponent);
begin
  inherited;
  FBorderWidth := 5;
  ControlStyle := ControlStyle + [csAcceptsControls];
end;

procedure TSomeControl.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
  inherited;
  InflateRect(Message.CalcSize_Params.rgrc0, -FBorderWidth, -FBorderWidth);
end;

procedure TSomeControl.WMNCHitTest(var Message: TWMNCHitTest);
var
  Pt: TPoint;
begin
  inherited;
  Pt := ScreenToClient(Point(Message.XPos, Message.YPos));
  if Pt.X < 0 then
    Message.Result := HTLEFT;
  if Pt.Y < 0 then
    Message.Result := HTTOP;
  if Pt.X > ClientWidth then
    Message.Result := HTRIGHT;
  if Pt.Y > ClientHeight then
    Message.Result := HTBOTTOM;
end;

procedure TSomeControl.WMNCPaint(var Message: TWMNCPaint);
var
  DC: HDC;
begin
  DC := GetWindowDC(Handle);
  SelectClipRgn(DC, 0);
  SelectObject(DC, GetStockObject(BLACK_PEN));
  SelectObject(DC, GetStockObject(GRAY_BRUSH));
  Rectangle(DC, 0, 0, Width, Height);
  ReleaseDC(Handle, DC);
end;

//---------------------------------------

procedure TForm1.Button1Click(Sender: TObject);
var
  C: TSomeControl;
  P: TPanel;
begin
  C := TSomeControl.Create(Self);
  C.SetBounds(30, 30, 120, 80);
  C.Parent := Self;

  P := TPanel.Create(Self);
  P.Parent := C;
  P.Align := alClient;
end;

end.
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Thanks Sertac. I guess I will somehow need to get a bitmap of whats underneath the control and paint it to the borders to make it look transparent. – Andy k Feb 14 '19 at 08:49
  • @Andy - Is what you are looking for full transparency or some kind of glossy effect? – Sertac Akyuz Feb 14 '19 at 14:27
  • I was hoping to get full transparency a la Windows 10 forms. I did try to set a region, which gave the effect but the sizer cursor stopped working in the region so it defeated the object. I have set the boder width to 3 pixels and colored in the parent color which is a half way house. I think google Chrome is completely borderless and painting all its borders inside because it has the effect on windows 7 as well as 10. – Andy k Feb 14 '19 at 22:15
  • To have fully transparent borders, you have to have WS_EX_TRANSPARENT window style, you won't be able to convince a sibling or the parent to paint an overlapped region otherwise. Do that by overriding CreateParams. Then do nothing in the WM_NCPAINT handler, or don't handle the message at all. Don't forget to paint the client area or the internal area will remain transparent too. – Sertac Akyuz Feb 15 '19 at 15:09
  • BTW I don't see anything unusual with chrome on W7, other than it seems to draw on glass frame. I don't have access to any W10 system, I don't like accessing it either.. – Sertac Akyuz Feb 15 '19 at 15:16
  • Yes your right, I must have just dreamt that. Also I did try deriving from TcustomtransparentControl, which I guess uses WS_EX_TRANSPARENT. But it just introduced a whole bunch of other problems to overcome. – Andy k Feb 19 '19 at 10:09