Live update of StringGrid when horizontal scroll bar is moving? - delphi

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.

Related

Drag/Drop to arrange vertically within ScrollBox

I was asked to make some changes to a project another developer did 10+ years ago in Delphi 7. This is a proprietary bit of code, so I'll be extremely specific.
The "Container" is a TScrollBox and the panels inside are TSpkRollPanel's -- a collapsible or expandable TPanel derivative.
I hope the image below explains everything. It's really simple. I'm supposed to make the TSpkRollPanel elements drag/drop vertically ONLY so they can be arranged in the desired order. I've spent a few hours getting up to speed on Delphi (Which I haven't seen in 10+ years)
If anyone could point me in the right direction, I'd appreciate it. I'm Delphi literate, just rusty.
With standard TPanel panels the following works fine, and most probably with your panels as well. The steps are the following:
Select all panels
Set Align property of all panels to AlTop
Set DragMode property of all panels to dmAutomatic
Switch to event view in Object Inspector
Double click in entry field of OnDragDrop to create event handler
Double click in entry field of OnDragOver to create event handler
If the names of the two created event handlers include identifier for a specific panel, you may want to rename the event handlers to reflect that they are common for all panels.
6 Finally, add code to event handlers
procedure TForm1.PanelDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
TPanel(Source).Top := TPanel(Sender).Top - 5;
end;
procedure TForm1.PanelDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := True;
end;
The solution works so that when a panel (A) is dropped on another (B) it (A) will take that panels (B) place in the alignment order, pushing (B) and the other panels down.

How to handle TListBox scrolled all the way down to last TListBoxItem in Delphi XE8?

I am trying to make a TListBox that initially loads 15 TListBoxItems in it and every time you scroll all the way to the bottom of the TListBoxItem add another 15 TListBoxItems. In a firemonkey multi-device project in Delphi XE8.
Now i can't figure out how to know if the user is scrolled all the way down in a TListBox. I've tried every event of the TListBox, but none of them seems to do it.
I think you need to come at it slightly laterally. Instead of thinking about it in terms of when the user scrolls to the bottom of the list, think about it in terms of when the last item in the list becomes visible, i.e. when the IsVisible property of the ListBox.ListItems[ ListBox.ListItems.Count - 1].IsVisible is TRUE.
This only becomes TRUE when the user has scrolled to the bottom of the list!
You could use the gesture manager or a simple timer to test the the status.
If the platform is Windows you can try something like this
var
i: Integer;
x:Double;
Begin
x := ListBox1.Height / ListBox1.ItemHeight;
i := GetScrollPos(ListBox1.Handle, SB_VERT);
if i + x >= ListBox1.Items.Count then
ShowMessage('It is at the end of scroll');
End;

How do you handle multiple TPanel's at design-time?

I have several TPanel's on my main form that I show/hide depending on options a user selects. The problem is that at design time I have to constantly move them around to edit them. Is there an easier/better way that others handle this situation?
If only one panel is visible at a time, then you might want to use a TPageControl to organize things. You can click the tabs to select which one to work on at design time, and then hide the tabs at run time, or hide the tabs all the time and select pages by setting the ActivePage property.
Use the Structure Pane to find the one you want, and then bring it to the front.
(source: embarcadero.com)
(But don't follow Embarcadero's example above; give your controls meaningful names so your controls are easier to tell apart.)
If the panels are stacked, you can reorder them by right-clicking on one and choosing Control->Bring to Front or Control->Send to Back from the context menu.
A caveat I've found when doing Panel1.Visible:= false in runtime is that it messes up with layout.
The solution I've found works is to do:
//Design time
//-----------
Panel1.BevelOuter:= bvNone; //Make the panel look flat.
//Run time when hiding the panel
//------------------------------
procedure HidePanel(APanel: TPanel);
var
H,W: integer;
begin
case APanel.Align of
alTop, alBottom: begin
APanel.Tag:= Max(APanel.Tag, APanel.Height);
APanel.Height:= 1;
end; {alTop, alBottom:}
alLeft, alRight: begin
APanel.Tag:= Max(APanel.Tag, APanel.Width);
Panel1.Width:= 1;
end; {alLeft, alRight:}
alNone: begin
H:= Max(APanel.Tag and $FFFF, APanel.Height);
W:= Max((APanel.Tag shl 16) and $FFFF0000, APanel.Width shl 16);
APanel.Tag:= (H or W);
APanel.Height:= 1;
APanel.Width:= 1;
end; {alNone}
//alClient: do nothing
end;
end;
//Run time when restoring the panel
//---------------------------------
procedure UnhidePanel(APanel: TPanel);
var
H,W: integer;
begin
case APanel.Align of
alTop, alBottom: begin
APanel.Height:= APanel.Tag;
APanel.Tag:= 0;
end; {alTop, alBottom:}
alLeft, alRight: begin
APanel.Width:= APanel.Tag;
APanel.Tag:= 0;
end; {alLeft, alRight:}
alNone: begin
H:= APanel.Tag and $FFFF;
W:= APanel.Tag shr 16;
APanel.Height:= H;
APanel.Width:= W;
APanel.Tag:= 0;
end; {alNone}
//alClient: do nothing
end; {case}
end;
Simply hiding the panels can mess up the careful alignment you've constructed in Designtime
(esp. when using splitters).
This code prevents that from happening.
It really only works visually when the panel has no Bevels set and the panelcolor equals the color of the control it's on top of.
I select the Frame or Panel using the Object inspector Combo then on the main menu click Edit--> bring to front
(which is similar to opening the structure view)
I also have used a TPageControl, and I kept the tabs visible at designtime. This gave me a designtime usability (via clicking on the tabs I want). Then at runtime, I hide the page tabs, and switch active pages on the page control using code, as a way of switching which pane is visible. However, this lead to some horrifically huge and complicated forms, which was in turn, the cause of many problems.
For your case, I would suggest that you consider refactoring each pane into its own Form or Frame. My preference would be to use Forms, not frames, and the reasons for this are well known and well documented in the Delphi world.
In my most well-structured applications, each "pane" (implementing using TForm, though, not TFrame) is separated into different units, and this solves both your design-time control problems, and also results in a more well structured overall solution.
While I think that the Structure Pane (someone else pointed out) is a great help to you, when you want to work with forms that are so complex that the regular designer visual tools start getting harder to use, it is also a good idea to consider breaking your form up, when you reach this point of "diminishing returns" of using the form Designer, on what is becoming one super-super complicated form.
It is much easier to use frames for this purpose. I usually create them at runtime and add them to the form as needed. It also tends to make the code much more readable because you can use the same component names (e.g. ed_Name or l_Welcome) on different frames without having name clashes rather than having ed_NameForPanel1, ed_NameForPanel3 etc.

Enable scroll bars in disabled TMemo control

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)

How Do I Add A TLabel To The Menu Bar in Delphi?

I use Beyond Compare (a great program), and was very impressed when it displayed a "New Version Available" label on its Menu Bar. I clicked on it, up popped an install new version box, it installed, the program restarted itself and there was the new version and no more label.
I thought that was a great feature. The label is there prominently on the menu bar where the user can't miss it. I've already got the update procedure, so all I had to do was add the label. That should be easy.
Here's the label where I want it:
(source: beholdgenealogy.com)
... Wrong. I couldn't figure out how to add a label there. The menu bar and the control area above it appear to be hands-off area for visual components. I couldn't place one there.
But I know it can be done, because Beyond Compare is a Delphi program.
Can anyone tell me what I have to do to put a TLabel in my Menu Bar or at least make it appear to be over the Menu Bar in the correct position?
For reference, I use Delphi 2009.
Conclusion: Christopher seems to have correctly figured out what the Beyond Compare people did. I've decided to implement the menu item, but without the customization of his "owner draw" solution. So I don't get the blue bold underline hyperlink look, but I also don't lose all the automatic things (like the Vista styling) that owner draw skips.
To space the menu item over to the right, I've added an item after the "Help" that has the caption " " and is disabled.
Thanks, Christopher. I was stuck thinking it must be a Label, but you saw around that.
Are you sure it's a label?
I haven't used the program, but it could just be a menu item, set to 'owner draw' and painted to look like a link?
http://sirmonkeys.com/images/updatelink.png
(done in Delphi 7)
procedure TForm1.MYITem1DrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
begin
acanvas.Font.Style := [fsUnderline,fsbold];
acanvas.Font.color := clblue;
acanvas.Brush.Style := bsClear;
acanvas.TextOut(arect.left+1,arect.top+1,'Link to Update...');
end;
procedure TForm1.MYITem1MeasureItem(Sender: TObject; ACanvas: TCanvas;
var Width, Height: Integer);
begin
width := 100;
end;
and then either a have an ImageList assigned to MainMenu1.Images
or set MainMenu1.OwnerDraw to true.
Beyond Compare's implementation is actually a TLabel. We use Toolbar 2000 for our menus and toolbars, so embedding a control on the menu directly is supported (with a correct background), and it has the advantage that it supports right-justified menu items.

Resources