Child form to use whole client area? - delphi

I'd like to show an MDI child window that will use the whole client area, ie. the grey part no the right-side of the taskpane, and have the child window show its titlebar and borders:
http://img149.imageshack.us/img149/3204/delphimdichildwindowwit.jpg
Here's the code, which doesn't work as planned:
procedure TForm1.RzGroup1Items0Click(Sender: TObject);
var
Form2 : TForm2;
begin
Form2 := TForm2.Create(Application);
//BAD : doesn't start at 0,0, and triggers horizontal scrollbar
Form2.Align := alClient;
//BAD : doesn't show titlebar and borders
Form2.WindowState := wsMaximized;
//BAD : window exceeds width -> horizontal scrollbar shown
Form2.top := 0;
Form2.Left := 0;
Form2.Width := Self.ClientWidth;
Form2.Height := Self.ClientHeight;
end;
Is there a way to do this, besides computing the coordinates myself (eg. ClientWidth, etc.)?
Thank you.

The following code will give you the rect of the MDI client area. Please note that fighting MDI is hard.
Form2.BoundsRect := GetMDIClientAreaBoundsRect(Form1);
function GetMDIClientAreaBoundsRect(MDIForm: TForm): TRect;
begin
if MDIForm.FormStyle = fsMDIForm then begin
if not Windows.GetClientRect(MDIForm.ClientHandle, Result) then
RaiseLastOSError;
end
else
raise Exception.Create('MDIForm is not an MDI form');
end;

The quickest way to do that would be the TILE command.
var
wFrm : TChildMDI;
begin
wFrm := TChildMDI.create(self);
wFrm.Show;
Tile;
end;
TILE is a method of TForm and if you only have 1 MDI child window it will do exactly what you want. With more that 1 it will then arrange all the visible child windows to fit in a similar layout.
Ryan.

Related

Canvas.textout doesn´t show text after a new series is made visible

So what i'm doing is display the x and y values of the mouse pointer on a teechart chart using the following code, inside the onmousemove event:
oscilografia.Repaint;
if ((x>236) and (x<927)) and ((y>42) and (y<424)) then
begin
oscilografia.Canvas.Brush.Style := bsSolid;
oscilografia.Canvas.Pen.Color := clBlack;
oscilografia.Canvas.Brush.Color := clWhite;
oscilografia.Canvas.TextOut(x+10,y,datetimetostr(oscilografia.Series[0].XScreenToValue(x))+','+FormatFloat('#0.00',oscilografia.series[0].YScreenToValue(y)));
edit1.Text:=inttostr(x)+' '+inttostr(y);
end;
The code works fine, but a problem happens when i make another series visible by selecting it on the legend: the text inside the box created by canvas.textout isn´t shown anymore.
The box is still there following the mouse, but without any text. So i would like a solution to this.
The basic problem is down to how painting works. Windows do not have persistent drawing surfaces. What you paint onto a window will be overwritten the next time the system needs to repaint it.
You need to arrange that all painting is in response to WM_PAINT messages. In Delphi terms that typically means that you would put your painting code in an overridden Paint method.
So the basic process goes like this:
Derive a sub-class of the chart control and in that class override Paint. Call the inherited Paint method and then execute your code to display the desired text.
In your OnMouseMove event handler, if you detect that the mouse coordinates text needs to be updated, call Invalidate on the chart.
The call to Invalidate will mark that window as being dirty and when the next paint cycle occurs, your code in Paint will be executed.
What is more, when anything else occurs that forces a paint cycle, for instance other modifications to the chart, your paint code will execute again.
Note, as an alternative to sub-classing, you can probably use the TChart event OnAfterDraw. But I'm not an expert on TChart, so am not sure. The main points though are as I state above.
From a comment you wrote, I see you followed this example.
Note it doesn't draw any rectangle; it only draws text, so I'm not sure to understand what box is following your mouse.
Also note the example calls Invalidate, as David Heffernan suggested in his answer.
Find below a modified version of the same example, painting a rectangle before the text.
procedure TForm1.FormCreate(Sender: TObject);
begin
Series1.FillSampleValues(10);
Chart1.View3D := False;
end;
procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var tmpL,tmpL2,ClickedValue : Integer;
tmpWidth, tmpHeight: Integer;
tmpText: string;
begin
clickedvalue := -1;
tmpL2:= -1;
With Chart1 do
begin
If (Series1.Clicked(X, Y) <> -1) And (not OnSeriesPoint) Then
begin
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Color := clBlack;
Canvas.Brush.Color := clWhite;
tmpText:=FormatFloat('#.00',Series1.XScreenToValue(x))+','+FormatFloat('#.00',Series1.YScreenToValue(y));
tmpWidth:=Canvas.TextWidth(tmpText)+10;
tmpHeight:=Canvas.TextHeight(tmpText);
Canvas.Rectangle(x+5, y, x+tmpWidth, y+tmpHeight);
Canvas.TextOut(x+10,y,tmpText);
OnSeriesPoint := True;
ClickedValue:= Series1.Clicked(x,y);
End;
//Repaint Chart to clear Textoutputted Mark
If (ClickedValue=-1) And (OnSeriesPoint) Then
begin
OnSeriesPoint := False;
Invalidate;
End;
tmpL := Chart1.Legend.Clicked(X, Y);
If (tmpL <> -1) And ((tmpL <> tmpL2) Or (not OnLegendPoint)) Then
begin
repaint;
Canvas.Brush.Color := Series1.LegendItemColor(tmpL);
Canvas.Rectangle( X, Y, X + 20, Y + 20);
Canvas.Brush.Color := clWhite;
Canvas.TextOut(x+15,y+7,FormatFloat('#.00',Series1.XValues.Items[Series1.LegendToValueIndex(tmpl)]));
tmpL2 := tmpL;
OnLegendPoint := True;
End;
If (tmpL2 = -1) And (OnLegendPoint) Then
begin
OnLegendPoint := False;
Invalidate;
End;
End;
End;

delphi form constraint doesn't work when maximized

i try to maximize width and hold height. i use delphi xe4, windows 7, 1,440 * 900 2 monitors.
height constraint usually works well but when it exceeds certain value which is 859 it doesn't work.
i guessed that it was because of windows snap feature but even after the turning off that it's same.
when i do this in the sub monitor which doesn't have taskbar and in the main monitor with taskbar auto hide it works well. it seems the trouble with taskbar.
any help to solve this please. thanks.
procedure TForm1.Button1Click(Sender: TObject);
begin
Constraints.MaxHeight := 859; // works well
WindowState := wsMaximized;
Caption := IntToStr(Height);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Constraints.MaxHeight := 860; // doesn't work
WindowState := wsMaximized;
Caption := IntToStr(Height); // maximized as 876
end;
procedure TForm1.FormConstrainedResize(Sender: TObject; var MinWidth, MinHeight, MaxWidth, MaxHeight: Integer);
begin
MaxHeight := 860; // doesn't works and is maximized as 876
end;
If you are trying to restrict the maximum size of the form when the user tries to maximise it then this will work. I have my own Form class descended from TForm called TRGNewForm and all my forms descend from that. However the scheme will work for the form you put the code in.
In the Interface section, in your Form class Public definition
{ Trap Sys Commands }
procedure WMSysCommand (var Msg: TWMSysCommand); message WM_SYSCOMMAND;
In the Implementation
procedure TRGNewForm.WMSysCommand (var Msg : TWMSysCommand);
begin
{ If MdiChild and Maximize button pressed, then use our own routine,
as Windows has a bug }
if (Msg.CmdType = SC_MAXIMIZE) and
(FormStyle = fsMDIChild)
then Maximise_Child_Form
else DefaultHandler (Msg);
end;
In Maximise_Child_Form you set the height and width as required.

Stop TCustomHint from centering itself around my point

I'm trying to use TCustomHint to show a message to my user that fades in and out nicely, to not be too distracting. However when I call ShowHint on my object with a point, the hint box appears to center itself around the point I give. What I would like is to have my box appear such that its top-left coordinate is the point given.
Here's the code I'm using so show the hint:
procedure ShowNotification(ATitle: UnicodeString; AMsg: UnicodeString);
var
Box: TCustomHint;
P: TPoint;
begin
Box := TCustomHint.Create(MyForm);
Box.Title := ATitle;
Box.Description := AMsg;
Box.Delay := 0;
Box.HideAfter := 5000;
Box.Style := bhsStandard;
P.X := 0;
P.Y := 0;
Box.ShowHint(P);
end;
I know that my point's X/Y coordinates are not relative to the form, and that's not the issue.
I've traced through what happens when I call ShowHint and it appears that if I can somehow control the final width of the underlying TCustomHintWindow inside of TCustomHint.ShowHint(Rect: TRect) then I may be in business.
So my question is: is there an obvious way to stop a TCustomHint from centering itself at my point? Or will I have to go through the process of inheriting, overriding the draw method, etc etc? I hope I'm just missing something simple.
There's no particularly easy way to do what you want. The TCustomHint class is designed to serve a very specific purpose. It was designed to be used by the TControl.CustomHint property. You can see how it is called by looking at the code for TCustomHint.ShowHint. The pertinent excerpts are:
if Control.CustomHint = Self then
begin
....
GetCursorPos(Pos);
end
else
Pos := Control.ClientToScreen(Point(Control.Width div 2, Control.Height));
ShowHint(Pos);
So, either the control is shown centred horizontally around the current cursor position, or centred horizontally around the middle of the associated control.
I think the bottom line here is that TCustomHint is not designed to be used the way you are using it.
Anyway, there is a rather gruesome way to make your code do what you want. You can create a temporary TCustomHintWindow that you never show and use it to work out the width of the hint window that you want to show. And then use that to shift the point that you pass to the real hint window. In order to make it fly you need to crack the private members of TCustomHintWindow.
type
TCustomHintWindowCracker = class helper for TCustomHintWindow
private
procedure SetTitleDescription(const Title, Description: string);
end;
procedure TCustomHintWindowCracker.SetTitleDescription(const Title, Description: string);
begin
Self.FTitle := Title;
Self.FDescription := Description;
end;
procedure ShowNotification(ATitle: UnicodeString; AMsg: UnicodeString);
var
Box: TCustomHint;
SizingWindow: TCustomHintWindow;
P: TPoint;
begin
Box := TCustomHint.Create(Form5);
Box.Title := ATitle;
Box.Description := AMsg;
Box.Delay := 0;
Box.HideAfter := 5000;
Box.Style := bhsStandard;
P := Point(0, 0);
SizingWindow := TCustomHintWindow.Create(nil);
try
SizingWindow.HintParent := Box;
SizingWindow.HandleNeeded;
SizingWindow.SetTitleDescription(ATitle, AMsg);
SizingWindow.AutoSize;
inc(P.X, SizingWindow.Width div 2);
finally
SizingWindow.Free;
end;
Box.ShowHint(P);
end;
This does what you asked, but honestly, it makes me feel rather queasy.

How to add components to TScrollBox dynamically one below another on button click?

I have created one TScrollBox. I have added the Label and Edit Box on it dynamically on Button click. For setting the location of component i have used the height,width,left,top property of components.
But when Scroll Bar gets appeared on screen after 5 components added, the next components location gets disturbed. and the next component is not placed in synchronous manner on ScrollBox.
The Top coordinate for controls placed on a ScrollBox need to take into account the amount of "scroll" that already took place. If you add the controls all at once this is not a problem, because the ScrollBox doesn't get the chance to "scroll".
If you add controls to the ScrollBox after it got a chance to "scroll", you need to take into account the amount of vertical "scroll" that took place. Here's a sample piece of code that will add labels to ScrollBox1, taking vertical scroll into account so controls don't overlap each other. Here I'm using the form's "Tag" property to hold the Top for the next control added, and I'm also using Tag to generate unique names for the labels (so you can see they're going into the ScrollBox at the correct coordinates).
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
begin
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
L.Top := Tag + ScrollBox1.VertScrollBar.Size - ScrollBox1.VertScrollBar.Position;
Tag := Tag + L.Height;
end;
An other approach I sometimes used is to keep track of the last control added and base the coordinates for the new control on the coordinates of that last added control:
var LastControl: TControl;
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
begin
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
if Assigned(LastControl) then
L.Top := LastControl.Top + LastControl.Height
else
L.Top := 0;
Tag := Tag + L.Height;
LastControl := L;
end;
And yet an other approach would be to find the lowest control and add your control based on it's coordinates:
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
Bottom, TestBottom: Integer;
i: Integer;
begin
// Find "Bottom"
Bottom := 0;
for i:=0 to ScrollBox1.ControlCount-1 do
with ScrollBox1.Controls[i] do
begin
TestBottom := Top + Height;
if TestBottom > Bottom then
Bottom := TestBottom;
end;
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
L.Top := Bottom;
Tag := Tag + L.Height;
end;

Resizing buttons so they are all the same width

I have a "wide" TPanel with several buttons on it (essentially a tool bar). All the buttons have Align=Left. I have created a function which will resize the buttons to the same size and calculate the width of them so they fill the entire TPanel. I call this function in the OnResize event handler of the TPanel.
procedure ScaleButtonsOnPanel;
var i: Integer;
begin
for i:=0 to mPanel.ControlCount-1 do begin
mPanel.Controls[i].Width := round(mPanel.width/mPanel.ControlCount-1)
end;
end;
The problem is if I minimize and then restore the form the layout of the buttons change from the design layout.
Can anyone offer a solution to having buttons on a panel which can be resized but maintain the design time order (in terms of left to right placement) ?
I do not really see your problem. But of course, you must set the position of the buttons, not only their size.
procedure TForm1.Panel1Resize(Sender: TObject);
var
i: Integer;
btnWidth: integer;
begin
btnWidth := Panel1.Width div Panel1.ControlCount;
for i := 0 to Panel1.ControlCount - 1 do
begin
Panel1.Controls[i].Left := i * btnWidth;
Panel1.Controls[i].Width := btnWidth;
end;
end;
This works very well.
See https://privat.rejbrand.se/panelresize.wmv.
OK, now I see. I think the alLeft is actually your problem. Controls with the same align tend to change their order. This is a well-known Delphi annoyance. Do it like I do above, instead. Just make sure that you go through the buttons in the right order. If you cannot rely on the ordering of Panel1.Controls, then you can do like this: Set the Tag property of each toolbar button to its position (0, 1, ...) in the toolbar then do
procedure TForm1.Panel1Resize(Sender: TObject);
var
i: Integer;
btnWidth: integer;
begin
btnWidth := Panel1.Width div Panel1.ControlCount;
for i := 0 to Panel1.ControlCount - 1 do
begin
Panel1.Controls[i].Left := Panel1.Controls[i].Tag * btnWidth;
Panel1.Controls[i].Width := btnWidth;
end;
end;
Have you tried to see if a TFlowPanel doesn't better suit your needs?

Resources