How to automatically resize TStringGrid row (DefaultRowHeight) to match the height of the font used?
I do something like
Grid.DefaultRowHeight:= Grid.Canvas.TextHeight('X') + 4;
but it is not working. For small fonts, the height of the rows is way too big.
Update:
It seems to be actually a problem with program's logic. If I change the font multiple times, the current height of the row matches the size of the font from previous font change event (it is one step behind).
I use this code to intercept font's size change:
procedure TStrGrid.CMFontChanged(var Message: TMessage);
begin
inherited; // let TControl react first
DefaultRowHeight:= Canvas.TextHeight('ApApM')+ 4;
end;
It acts as if the procedure will be:
begin
DefaultRowHeight:= Canvas.TextHeight('ApApM')+ 4;
inherited;
end;
(like first it changes the height, then it actually sets the correct font size - therefore the height is one step behind)
It has nothing to do with this order: both the inherited CMFontChanged message handler and the DefaultRowHeight property setter call invalidate. It is due to the current font setting which is not yet updated:
procedure TStrGrid.CMFontchanged(var Message: TMessage);
begin
Canvas.Font := Font;
DefaultRowHeight := Canvas.TextHeight('Ap') + GridLineWidth + 3;
end;
Explanation:
Invalidate only flags windows to repaint the window (the grid) somewhere in future. That happens certainly after obtaining the text height. As alternative you can call Repaint prior to the request for the new text height, but that would result in a double repaint, hence setting Canvas.Font.
When it comes to event ordering related issues then the easiest and quickest solution often was posting yourself a message via PostMessage (here from your CMFontChanged handler) and doing the update when receiving the posted message.
This has the advantage that it doesn't interfer with whatever the grids need to do to update its inner state.
Related
Learning Delphi (have a ways to go), using Rio.
I figured out how to use a colored background in TStringGrid rows - but it looks like I need to refresh when data in those rows changes (so as to get different colors to show up based on the data changes).
I thought that just setting the cell values to their then-existing values would cause the refresh. But it didn't. I could tell for sure that it didn't - because I had a debug breakpoint placed within the StringGrid1DrawCell procedure - and that breakpoint was not hit.
The code that I had been using to hopefully cause the refresh in TStringGrid was as follows (note: S is defined as a String):
S := StringGrid1.Cells[1, i];
StringGrid1.Cells[1, i] := S;
Is the basic assumption (that just setting/resetting values of the cell contents causes a refresh) in error?
If the idea is right, but the method is wrong: could you let me know what to do differently?
The OnDrawCell event is fired only when a given cell needs to be painted onscreen.
Setting a specific cell's value will invalidate only that cell, thus triggering a repaint of only that cell, not the cell's entire row, or the grid as a whole.
If you need to trigger a repaint of an entire row, call the grid's (protected) InvalidateRow() method, eg:
type
TStringGridAccess = class(TStringGrid)
end;
procedure TMyForm.DoSomething;
begin
...
StringGrid1.Cells[1, i] := ...;
TStringGridAccess(StringGrid1).InvalidateRow(i);
...
end;
If you need to trigger a repaint of the entire grid, call the grid's (public) Invalidate() method, eg:
StringGrid1.Cells[1, i] := ...;
StringGrid1.Invalidate;
Delphi controls have AutoSize property which is exposed e.g. in TPanel. It adjusts the width/height of the panel depending on the content.
Apparently it does nothing when the panel is invisible, and does no realigning later when it's set to visible. So if I put some controls into it and then make it visible, the size is not adjusted.
I can trigger adjusting size by setting height to any value in FormShow:
procedure TForm1.FormShow(Sender: TObject);
begin
Panel1.Height := Panel1.Height + 1; //triggers auto-resize
end;
But I have to do this manually for every control which has AutoSize on. I'm bound to forget something.
Are there better ways to fix this, preferably once and for all?
I don't think there's much that you can do. A better way to for the re-sizing is to add a call to the Realign method of the panel immediately after you make it visible.
You could hook into the CM_VISIBLECHANGED message and force the matter there, for auto-sized controls. For instance, using an interceptor:
type
TPanel = class(Vcl.ExtCtrls.TPanel)
protected
procedure CMVisibleChanged(var Message: TMessage); message CM_VISIBLECHANGED;
end;
procedure TPanel.CMVisibleChanged(var Message: TMessage);
begin
inherited;
if Visible and AutoSize then
Realign;
end;
Its been a while since i used delphi, but one thing i remember is that the controls did play a bit of mind games on me, most of the time it was because the rendering engine did not refresh the form and the controls.
If you have the control set to auto-resize i would suggest checking if form1.refresh or panel1.refresh since its been a few years since i played with it ( delphi 7 ) i might confuse refresh with repaint. which some controls had, which initiated the size calculation before repainting the control. since delphi controls are open source you can go in to the apropriate pas file to find the control and look at refresh/repaint to see if you can persist the auto-resizing.
hope that helped.
I have a frame that is being placed on a form. I expect to be placing a number of instances of this frame on the form.
It has a drawgrid with 2 columns and in the OnResize event I make the 2nd column extend to the end of the available space. That works when the form is manually resized with the frame Align set to alTop. But when the form first appears even though FrameResize gets called it has no effect. (Though it did have the desired effect when I put a break point on it).
So, what I am doing now is calling the FrameResize from the forms OnShow handler, but that is ugly. The frame should be able to appear correctly formed without help from the Form.
Any ideas? I did try overriding SetParent, but that didn't work. Using Xe2.
TIA
Mark
I have solved it with advice from Peter Below, Delphi Team B Delphi member.
I overrode the frame's set bounds. It was getting called even before the component variables were set, so it looks like this
procedure TfaDupDisplay.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); // Had to use SetBounds because OnRezise was not working
var grid: TDrawGrid;
begin
inherited;
if pnlWebData = nil then
exit;
pnlWebData.Width := Width div 2;
for grid in TArray<TDrawGrid>.Create(grdData, grdDup) do
grid.ColWidths[1] := grid.Width - grdData.ColWidths[0];
end{ SetBounds};
This happens in all Delphi up to XE3:
Create a form and put a panel on it. Anchor the panel to [akLeft, akTop, akRight, akBottom], but leave space between it and the borders.
Add a button which calls RecreateWnd()
Run the app. Resize the form so that the panel is hidden because it's less than 0 pixels in size due to anchoring. Press the RecreateWnd button.
Resize the form back and note that the panel's anchoring is broken.
As long as I can remember myself using Delphi, anchors were always impossible to use because of this. Resize the form, then dock it: the window is recreated, your layout is broken.
I wonder if there's some sort of workaround?
Update
Two workarounds are available in the comments, one proven and stable but with form blinking, another one experimental but potentially more thorough and clean.
I'm not going to vote for either one for a while, as one of those is mine and I'm not even sure it is stable. Instead, I'll wait for some public input.
The two options I have used of which neither is really ideal for problems with the bottom and right anchors are:
Make the window big again before calling or causing to be called RecreateWnd();, then make it small again. Has to be visible before you make it small again however.
Set the Form's constraints so it can't be re-sized so small that stuff ends up hidden.
An example that flashes the larger form, use height and width large values enough so the panel is not hidden:
procedure TForm1.Button1Click(Sender: TObject);
Var
OldWidth, OldHeight : integer;
begin
OldWidth := Form1.Width;
OldHeight := Form1.Height;
Form1.Visible := false;
Form1.Width := 1000;
Form1.Height := 800;
RecreateWnd();
Form1.Visible := true;
Form1.Width := OldWidth;
Form1.Height := OldHeight;
end;
Turns out that the function which breaks everything is UpdateAnchorRules. TControl stores FOriginalParentSize and it's own original size in FAnchorRules, and uses that to auto-resize as parent resizes. UpdateAnchorRules() takes current parent size and current control Width and Height and saves those into FOriginalParentSize and FAnchorRules.
If everything worked properly that would have no effect during normal resizes, as the control and it's parent change size in accord.
But when the control Width is less than zero due to anchoring, Windows, and consequently Delphi still considers it 0. If UpdateAnchorRules is called at that point, it saves wrong, out-of-accord 0 value for original width. After this the layout is beyond repair.
(If it's not called, Width continues to be updated in proper relation to parent Width due to original sizes preserved)
Turns out anything which involves creating a window handle calls UpdateAnchorRules twice: first in WinAPI CreateWindow as it dispatches WM_SIZE before returning (and WM_SIZE handler calls UpdateAnchorRules), and second, explicitly in CreateHandle after handle creation.
It would seem that as long as we can disable UpdateAnchorRules for the duration of CreateHandle, we will succeed. But there are explicit calls to UpdateAnchorRules in CreateHandle, which means someone thought there needs to be an adjustment of Anchor rules after handle creation.
So perhaps I'm missing something, and by disabling it something will break?
Anyway, there are two ready ways to disable UpdateAnchorRules: to set FAnchorMove or to set csLoading. First one is no good as there's code which clears it midway through RecreateWnd and then calls UpdateAnchorRules again.
Second one works and here's a solution:
type
TComponentHack = class helper for TComponent
public
procedure SetCsLoading(Value: boolean);
end;
procedure TComponentHack.SetCsLoading(Value: boolean);
var i: integer;
begin
if Value then
Self.FComponentState := Self.FComponentState + [csLoading]
else
Self.FComponentState := Self.FComponentState - [csLoading];
for i := 0 to Self.ComponentCount-1 do
if Self.Components[i] is TControl then
TControl(Self.Components[i]).SetCsLoading(Value);
end;
procedure SafeRecreateWnd();
begin
MyControl.SetCsLoading(true);
try
MyControl.RecreateWnd(); //or any operation which triggers it -- such as docking or making the window visible first time after RecreateWnd()
finally
MyControl.SetCsLoading(false);
end;
end;
Disclaimer:
I have no idea what else will be broken by running TControl operations with csLoading set.
Better alternative would be to hook UpdateAnchorRules procedure and add another flag check specifically for this purpose, but that'd require either reimplementing UpdateAnchorRules completely (prone to breaking on different versions of Delphi with different original UpdateAnchorRules) or inventing some way to call original UpdateAnchorRules which is usually destroyed by rewriting it with a hook.
What options/properties should use to show a main from scrolbars when I want to?
or always visible in Delphi 2010
The help is as too often useless
thanks
Pw
#Philippe, you can use the ShowScrollBar function and the HorzScrollBar, VertScrollBar propeties to do this.
check this code
procedure TForm1.FormCreate(Sender: TObject);
begin
HorzScrollBar.Range := 10000; // set the range to an higher number
VertScrollBar.Range := 10000; // set the range to an higher number
ShowScrollBar(Handle, SB_BOTH, True);
end;
If you set AutoScroll = true, they should show up if needed. That is, if any visual component is placed outside of the visible client area.
If you do not have any components 'off-screen', why would you need the scrollbar showing?
Anyway, you can set Horz-/VertScrollBar.Range to anything larger than the clientheight/width, and they will show up.
If you need the scrollbar for something else, you can always drop a TScrollBar component on the form.