Enable scroll bars in disabled TMemo control - delphi

Is there a way how to enable scroll bars in disabled TMemo component ? I want let the users scroll the content even if the control's Enabled property is set to False.
I know about the possible workaround with ReadOnly and color changes like in disabled state, but this would help me a lot.
Thanks in advance :)

A control can be disabled or enabled, but not half dis- and half enabled. (And, for the nit-pickers amongst us, I think no hack should make it so :-), for the reason given below).
Using ReadOnly is the easiest solution. Be mindful though with the color changes to not make the control look disabled. That would also be very confusing for the user with regard to recognizing enabled/disabled controls. It would be better to make it look like a scrollable multi-line label. That usually is done by setting the (background) color equal to the color of its parent.
Haven't used the solution suggested and linked by #HalloDu, but that looks like a good alternative.

Well, it is not exactly, what you want, but the effect is the same. Look at this article where an ViewOnly Property for WinControls is implemented, which I found quite useful over the years. LINK

That's not perfect way but it works :
Use ScrollBar comp. adjacent to Memo.
procedure TForm9.FormCreate(Sender: TObject);
begin
Memo1.ScrollBars := ssNone;
ScrollBar1.Min := 0;
ScrollBar1.Max := Memo1.Lines.Count div (Memo1.Height div 13);//13 is height of a line in memo
end;
procedure TForm9.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
begin
if ScrollCode in [scPageDown, scLineDown] then
SendMessage(Memo1.Handle, WM_VSCROLL, SB_PAGEDOWN,0)
else if ScrollCode in [scPageUp, scLineUp] then
SendMessage(Memo1.Handle, WM_VSCROLL, SB_PAGEUP,0);
end;

There is a way.
Place entire TMemo inside a TScrollBox.
When you fill the memo with text, adjust the height and width to accommodate the size of the text (that's another question but I'm sure it can be done)

Related

How to work around AutoSize's being off while invisible?

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.

The anchor property of components when the application is maximized in Delphi

I have three buttons placed at the right on my form. The buttons' anchor property parameters akTop, akRight are set to true, the other ones left to false so that the buttons always remain at the right side near the boarder when the form is resized. Then I set the form's WindowState property to wsMaximized so that it covers the whole screen when run at start-up. But when I start the application the buttons are closer to the middle rather than on the right. But when I resize the form at design-time everything seems to work just fine.
Here are some snapshots to show you exactly what I mean:
At design-time:
At run-time:
Please, explain what I'm doing wrong and how to fix this so that it works as intended.
This looks like the buttons are being created with their designed positions, the form is then set to Maximized, then the anchor properties are set or put in place.
In design time the anchors are already set and so that’s why you see them move as you want. To prove my theory on this make the form much smaller, run the application and notice that the items are in their smaller design time locations.
An easy fix to get what you want. Keep the Window state at wsNormal and on FormShow (which occurs after Create) do this:
procedure TForm1.FormShow(Sender: TObject);
begin
self.WindowState := wsMaximized;
end;
You will see the results you want.
I have seen your answer in one of those tutorials. But realy dont remember was which one. You can watch all videos, even you will learn more things. it will not time waste all the way.
Link: Learn Delphi TV
Also you can try something like below if you are lazy enough to watch videos. Put this code in form resize:
buttoncreate.left := panel.width - (buttoncreate.width + buttonedit.width + buttondelete.width);
buttonedit.left := panel.width - (buttonedit.width + buttondelete.width);
buttondelete.left := panel.width - buttondelete.width;

Workaround for anchors being broken when recreating a window?

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.

Live update of StringGrid when horizontal scroll bar is moving?

In Delphi 2010, I need to display a grid that has a horizontal scroll bar with about 15 columns x 5 rows.
I chose to use a StringGrid.
However, while the mouse button is down dragging the horizontal scroll bar, I want the grid to scroll live.
The StringGrid component, it appears, does not scroll live. It waits until the mouse button is released before updating the column and scrolling if necessary.
Also, the horizontal scroll bar button (is that what it's called) is not proportional to the number of columns. And for a down-arrow when on the bottom row to move to the top of the next column to the right...
These seem like common needs, so I was surprised not to find them in TStringGrid.
Any suggestions on a way around these two problems? I can use a DbGrid or other standard component, but my preference is to not use a commercial grid if I can avoid it. And I'm not going to use shareware or freeware...
TIA
For the first question, you can set goThumbTracking in the StringGrid's Options at design-time, or at run-time: StringGrid1.Options := StringGrid1.Options + [goThumbTracking];
For the third question, you can provide the functionality you need by using keyboard event handlers of the control. An example;
procedure TForm1.StringGrid1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
StringGrid: TStringGrid;
begin
StringGrid := Sender as TStringGrid;
case Key of
VK_DOWN:
if StringGrid.Row = StringGrid.RowCount - 1 then begin
Key := 0;
StringGrid.Row := StringGrid.FixedRows;
if StringGrid.Col = StringGrid.ColCount - 1 then
StringGrid.Col := StringGrid.FixedCols
else
StringGrid.Col := StringGrid.Col + 1;
end;
VK_UP: //...;
VK_RIGHT: //;
VK_LEFT: //;
end;
end;
For the second question, the scrolling code seems to be buried in private methods of TCustomGrid. I have no clue how to achieve that..
If noticed you are not interested in third party components - Freeware, I am not fond of these either, but we all must make sacrifices sometimes if we want to get the problems solved. This is one of these sacrifices! This component is to good to be ignored. You will not create something like it yourself if you don't have a couple of years of free time.
Either write a new component based on TStringGrid (I would not - it is not the best tool in the box to begin with)
But take some time and learn TVirtualStringTree. The component is years ahead of TStrignGrid. The source is available and there are many who uses it.
And there are events already implemented to react on scrollbar changes
OnScroll, OnShowScrollbar
http://www.delphi-gems.com/index.php?option=com_content&task=view&id=12&Itemid=38
Search on stackoverflow and you can read much more about tvirtualstringtree
Second the suggestion to use TVirtualStringTree. Working with the TStringGrid component is like stabbing yourself in the belly with a rusty scissor.

GridPanel does not adjust at first resize

I have a problem I am unable to solve even though I spend long time trying to do it.
I usually use GridPanels to align controls on forms. It has, however, an annoying bug. When the GridPanel align mode is alClient and I maximize its parent window, the GridPanel adjusts to the new size of that window, however, the controls laying on the grid do not. They stay in the same position they were before window resize.
It happens only at the first window's maximization. If the window is first resized manually, everyting is OK. I think the grid adjusts its child controls after the second resize event (??).
What to to do make GridPanel work properly if it comes to this bug? It might be enough to send a message to it (but what message?), I do not know. I tried to use Realign, Refresh etc., but they did not help.
Thanks for your help in advance,
Mariusz.
Ah, I've had similar issues as well. It might be related to a resizing problem in the VCL. You might want to try the fix by Andreas Hausladen. It seems to work for me in most of the cases.
Changing the width / invalidating the control doesn't work for me (something changed with recent versions of RAD Studio?).
Anyway a similar, simple workaround along that line is:
procedure TForm1.FormResize(Sender: TObject);
begin
GridPanel1.ControlCollection.BeginUpdate;
GridPanel1.ControlCollection.EndUpdate;
end;
I found one trick.
in OnResize event of parent of gridpanel, change gridpanel's width by 1 pixel.
then gridPanel will notice size changed, then rearrange sub-controls in gridpanel..
sample is below..
procedure TForm1.FormResize(Sender: TObject);
begin
GridPanel1.Width := GridPanel1.Width - 1; // subtract 1
GridPanel1.Width := GridPanel1.Width + 1; // recover width by adding 1
end;
I have had this error too, on several projects. I'm not sure how I solved this (it was on projects for my previous employer, I don't have access to this source code anymore). I think I had te redraw / refresh that parent frame or form on which the GridPanel was placed.
on the resize of the owner call GridPanel.Invalidate.
I didn't test it. I hope it's work.

Resources