draw a close button in each Ttabsheet of a TPageControl - delphi

I want to implement a close button on a PageControl and I have read this question also How to implement a close button for a TTabsheet of a TPageControl
The thing is I can't figure it out how to implement the code provided in the answer of Ulrichb... are they building a new component descendant from TPageControl or not? if someone could explain where to write that certain code i would be thankfull! I have a single teacher who knows a little bit of delphi at my school but he couldn`t help me out..and I am sorry if this is a silly question but i am new to delphi and programming.

The code in the question you link to does not create a new component. Instead it implements custom drawing by using events of the page control. Specifically these events:
OnDrawTab
OnMouseDown
OnMouseMove
OnMouseLeave
OnMouseUp
You must use the Delphi form designer to connect these event handlers up to the matching events to make the code work.
This approach was probably chosen for simplicity when answering that question but it does not scale to an application with many forms that have page controls. In that situation you would want to derive a new page control component.
If you do that then, rather than using events, you need to override the following methods:
DrawTab
MouseDown
MouseMove
MouseUp
In addition to this you must replicate the OnMouseLeave behaviour. That requires a message handler.
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
....
procedure TMyPageControl.CMMouseLeave(var Message: TMessage);
begin
inherited;
if Message.LParam=0 then
begin
// move OnMouseLeave code here
end;
end;

Related

MDI-Form ignores StyleElements seClient

i have a problem with the VCL-Styles and MDI-Form. I want to use the VCL Styles, but i also want to draw the background (image) of my MainForm (MDI) by myself. This worked fine without VCL Styles, but when a Style is active the background image of the MainForm isn't shown.
I checked out the StyleElements for the MainForm, but exclude the seClient is ignoerd and the background image isn't shown.
When i exclude the seClient and seBoarder the image is shown again. Obviously the Form Boarder lost the Style, which is also not that what i want.
The image is drawn at the Canvas in the ClientWndProc by the messages WM_ERASEBKGND, WM_VSCROLL and WM_HSCROLL. With the Styles, it looks like this events didn't raise. Is there any way the get the image at the form background with VCL Styles active?
The main point to realize here is that form styled fsMDIForm is a very special TWinControl that manages two window handles instead of one - TWinControl.Handle and TForm.ClientHandle. While the first handle is the form window itself the second is MDI client window (container-like for MDI child windows inside MDI parent).
TFormStyleHook hooks both window procedures and introduces new method TFormStyleHook.MDIClientWndProc, which processes messages sent to MDI client. This method luckily virtual. It does some pre-processing of messages and then calls the original hooked procedure. The sad part is that it prevents calling the old procedure for WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCPAINT and WM_ERASEBKGND. Even worse is that on WM_ERASEBKGND it paints the client area background directly using StyleServices.
Thanks to the above the subclassing of TFormStyleHook for MDI forms a PITA. I see multiple design flaws here:
Missing virtual TFormStyleHook.PaintMDIClientBackground similar to TFormStyleHook.PaintBackground.
No way to control/access over the original MDI client proc without hacking (hidden in private field FMDIPrevClientProc).
Disability to control styling of MDI client window via TForm.StyleElements (as noted by OP).
So what is the workaround? The easiest I can see is creating a custom style hook:
type
TMainFormStyleHook = class(TFormStyleHook)
public
procedure MDIClientWndProc(var Message: TMessage); override;
end;
{ TMainFormStyleHook }
procedure TMainFormStyleHook.MDIClientWndProc(var Message: TMessage);
begin
if Message.Msg = WM_ERASEBKGND then
begin
{ TODO: Paint background to TWMEraseBkgnd(Message).DC }
Message.Result := 1;
end
else
inherited;
end;
and applying it to your MDI parent:
type
TMainForm = class(TForm)
private
class constructor Create;
class destructor Destroy;
{ ... }
end;
{ TMainForm }
class constructor TMainForm.Create;
begin
TCustomStyleEngine.RegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
class destructor TMainForm.Destroy;
begin
TCustomStyleEngine.UnRegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
Note that you still need to keep painting background in MDI parent form in case the VCL styles are disabled, so it's worth creating method TMainForm.PaintMDICLientBackground(DC: HDC) and call it from both places.
I would argue that this is a bug in VCL. How about you guys?

Focus control after form is shown

I have some forms that help me search for a product or a customer. When i open these forms i want the TEdit control that i type into for searching to be focused. I've been using a Timer for that but i've been searching for a more legit way to do this as this causes errors sometimes if the control is told to be focused when the form is not visible yet.
I've tried to use a windows message AfterShow that is called on the end of OnShow event of my Form. It doesn't work as the other simpler solutions of ActiveControl or SetFocus. The window message code is this.
const WM_AFTER_SHOW = WM_USER + 444;
private
procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;
procedure Tproducts_edit_form.WmAfterShow(var Msg: TMessage);
begin
self.ActiveControl:= search_txt;
//showmessage(Screen.ActiveControl.Name);
//PostMessage(search_txt.Handle, WM_SETFOCUS, 0, 0);
end;
Strange thing is that if uncomment both the showmessage and the postmessage, the TEdit gets the focus correctly. If i don't, the form opens but the TEdit is not focused even if the Screen.ActiveControl.Name tells me that the control i want has the focus.
Any ideas?
It is correct to use Form.ActiveControl (not Screen.ActiveControl) property to set focus on control, but use it in OnShow, not in OnCreate etc..:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
ActiveControl = Edit1;
}
If it doesn't work, maybe because of manual interfering with window message handler, message queue.
The windowsmessage technique is working and the TabOrder of the control to be focused indeed has to be zero.
The problem i had was lying in the DevExpress Bar that my control is docked. The way these bars work makes it impossible to focus a non-DevExpress control that is docked into a DevExpress Bar.

Why doesn't OnUpdate trigger for invisible components [duplicate]

This question already has answers here:
How can I use an action to determine a control's visibility?
(3 answers)
Closed 8 years ago.
When I make a component invisible by setting the connected TAction to invisible, the onupdate event will not trigger anymore. To recreate, do the following.
Create a new VCL forms application
Drop a button, a checkbox and an actionlist on the form.
Create a new action, and connect the button to it.
Write the following code for the actions OnExecute and OnUpdate event:
procedure TForm1.Action1Execute(Sender: TObject);
begin
ShowMessage('Test');
end;
procedure TForm1.Action1Update(Sender: TObject);
begin
TAction(Sender).Enabled := not CheckBox1.Checked;
TAction(Sender).Visible := TAction(Sender).Enabled;
end;
Run the application. The button is visible, and works properly. Check the checkbox, and the button disappears. Uncheck the checkbox. The button doesn't appear. In fact, if you put a breakpoint in Action1Update, you'll never get to it. Why is this, and how do I fix it?
No need to fix this, it works as designed. Only visible controls need to update their state, so only actions whose linked controls are visible are updated. When you hide the button there's no more reason to update the action.
Have the OnUpdate only call a separate routine that does what is required. Then you can call that routine from other places. Action lists were designed for that.
I understand what you're trying to do, and it makes sense that you would want it to work that way. However, here's a workaround for the way it does work.
You can update other controls in an OnUpdate also. You're not limited to updating the control that receives the notification. So, in the action for the control that determines visibility, you can set the visibility of the other controls there. In your case, that's the checkbox:
Create a new action (Action2) and assign it to Checkbox1.
Then in the checkbox action's OnUpdate:
procedure TForm1.Action2Update(Sender: TObject);
begin
Button1.Visible := TAction(Sender).Checked;
end;
Be sure to assign an OnExecute to the checkbox as well. Something as simple as this is fine:
procedure TForm1.Action2Execute(Sender: TObject);
begin
TAction(Sender).Checked := not TAction(Sender).Checked;
end;
To me, this still makes logical sense. You'll be able to look in one spot to see all of the controls whose visibility relies on that checkbox being set.
You can override the InitiateAction method on the form. This will happen whenever the application goes idle, just as on OnUpdate event does for each action.

Not able to use canvas function in Delphi 7

I am beginning to learn GDI graphics in Delphi 7. I am having problems in drawing Ellipses , Text etc. on my Main form. Basically I use this code:
Form1.Canvas.TextOut(10,10,'sss');
Is this Canvas Property required to be associated with the Form? I haven't done any thing like that. Help will be appreciated.
Make sure you put all painting code in the form's OnPaint event handler (documentation). This handler is called whenever the form needs to be repainted.
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.TextOut(10,10,'sss');
end;

Making a TPageControl flat in Delphi 7

I don't know whether this question can be answered here, but I hope it will.
I wrote a simple text editor in Delphi 7 that serves as my primary IDE for writing C code under Windows. I run Windows in a VM and I needed something light.
In any case, it uses a TpageControl that gets a new tab whenever you open or create a new file. Pretty standard.
Now, the TPageControl under Delphi has no flat property.
NO I don't mean setting the tab style to tsButtons or tsFlatButtons
the borders cannot be set to "none" and it looks pretty bad when you add a text editor into the tab control.
Is there any way to make a TpageControl flat?
EDIT:
On an open source PageControl that supports flat here's what I found:
procedure TCustomTabExtControl.WndProc(var Message: TMessage);
begin
if(Message.Msg=TCM_ADJUSTRECT) and (FFlat) then
begin
Inherited WndProc(Message);
Case TAbPosition of
tpTop : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Top:=PRect(Message.LParam)^.Top-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpLeft : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Left:=PRect(Message.LParam)^.Left-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpBottom : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Bottom:=PRect(Message.LParam)^.Bottom-4;
PRect(Message.LParam)^.Top:=0;
end;
tpRight : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=PRect(Message.LParam)^.Right-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
end;
end else Inherited WndProc(Message);
end;
The thing is when I tried something similar on the main application it won't work. It won't even compile.
When the tabs are drawn as buttons, no border is drawn around the display area, so set the Style property to tsButtons or tsFlatButtons. (For non-VCL programmers, this is equivalent to including the tcs_Buttons window style on the tab control.)
An alternative is to use a TNotebook. It holds pages, but it doesn't do any painting at all. You'd have to provide the tabs yourself, such as by setting the tab control's height equal to the height of the tabs, or by using a TTabSet. (TTabSet is available in Delphi 2005; I'm not sure about Delphi 7.)
Regarding the code you found, it would be helpful if you indicated why it doesn't compile, or if you gave a link to where you found it, since I suppose the compilation error was because it refers to fields or properties of the custom class rather than the stock one. Here's what you can try to put it in your own code, without having to write a custom control.
Make two new declarations in your form like this:
FOldTabProc: TWndMethod;
procedure TabWndProc(var Msg: TMessage);
In the form's OnCreate event handler, assign that method to the page control's WindowProc property:
FOldTabProc := PageControl1.WindowProc;
PageControl1.WindowProc := TabWndProc;
Now implement that method and handle the tcm_AdjustRect messsage:
procedure TForm1.TabWndProc(var Msg: TMessage);
begin
FOldTabProc(Msg);
if Msg.Msg = tcm_AdjustRect then begin
case PageControl1.TabPosition of
tpTop: begin
PRect(Msg.LParam)^.Left := 0;
PRect(Msg.LParam)^.Right := PageControl1.ClientWidth;
Dec(PRect(Msg.LParam)^.Top, 4);
PRect(Msg.LParam)^.Bottom := PageControl1.ClientHeight;
end;
end;
end;
end;
You can fill in the other three cases if you need them. Tcm_AdjustRect is a message identifier declared in the CommCtrl unit. If you don't have that message in that unit, declare it yourself; its value is 4904.
I suspect this doesn't stop the control from drawing its borders. Rather, it causes the contained TTabSheet to grow a little bigger and cover up the borders.
I'm using Delphi XE8 and the following seems to do the trick:
ATabControl.Tabs.Clear;
ATabControl.Style := TTabStyle.tsFlatButtons;
ATabControl.Brush.Color := clWhite;
You could always use a commercial solution. I would strongly recommend Raize components, which support flat TPageControls with tabs. The component set is very easy to work with, and supports numerous visual enhancements which in my opinion give a better feel to any application.
(source: raize.com)
Drop two TPageControls, one with tabs as Tabs, with a global height equal to the tabs, and one with flatbuttons and Tabvisible properties set to false, which would be aligned under the first one. Then make sure the tab change on the first TPagecontrol makes the tabs also change in the second one.

Resources