How do I use DoMouseWheel to scroll a line at a time? - delphi

I've written a grid control and would like to add support for the mouse wheel to it. I thought it would be as simple as overriding the DoMouseWheel virtual method, but there is a bit of a problem with it.
You can set the number of lines to scroll at a time in Control Panel and the default there is three. And this makes perfect sense when scrolling through a document or web page, but on a grid, I think the expectation is rather to scroll a line at a time. But it seems that Delphi's wheel support will call DoMouseWheel three times for every notch that I scroll, meaning that I can only scroll to each third line in the grid (or whatever that global setting is).
How do I go about scrolling a single line at a time for every turn of the mouse wheel?
Update: The short answer here is to simply set Result to True after scrolling - then it doesn't scroll three times, but only once.

Just copy the code from the TCustomGrid class, which overrides both DoMouseWheelDown() and DoMouseWheelUp() to scroll exactly one line at a time.

In general, it is not a very good idea to fight against the system defaults and/or the user preferences. In this case means that you should respect whatever the system or the user has decided to set in the scrolling time.
Having said so, if you really believe that the multiscroll effect is totally wrong and misleading for the kind of component you want to drive, you might envision a way to get rid of this. You could try to set some timer and ignore all but one of the mouseWheel events that happen in a given lapse of time (in the range of milliseconds). One thing you should do is to set a configuration option in your program, to let the user to turn off this behaviour.

In my case, I used the JVDBGrid component, but I think this work for DbGrid too. You may overwrite the following functions: OnMouseWheelDown and OnMouseWheelUp.
E.g.:
Type declaration:
type
TMyGrid = class(TJvExDBGrid);
Implementation
procedure TFExample.JvDBGrid1MouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
Handled := TMyGrid(Sender).DataLink.DataSet.MoveBy(1) <> 0;
end;
procedure TFExample.JvDBGrid1MouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
Handled := TMyGrid(Sender).DataLink.DataSet.MoveBy(-1) <> 0;
end;

Related

How to ignore the mouse wheel scrolling for TComboBox controls? [duplicate]

This question already has answers here:
How to suppress mouse wheel in TcxComboBox
(1 answer)
Suppress mouse scroll events on specific controls but NOT their parent?
(1 answer)
Closed last year.
I've found numerous things in this regard, but they're either for third-party controls, different specific situations, or for a completely different language. What I need should be fairly simple.
I have a TDBGrid with a TComboBox placed over a specific cell. As the user scrolls through this grid, the combo box moves along to the corresponding cell. User is further able to change the value of this combo box to update the database.
However, when using the mouse wheel to scroll, if the mouse pointer just happens to be over this combo box, it ends up changing the value of the combo box, rather than scrolling the grid.
How do I suppress the scrolling in the combo box?
I am answering this QA style, because I found such a simple solution on my own which I couldn't find on various forums.
The simplest method is to add an OnKeyDown event handler to the TComboBox, and add the following:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN] then
Key:= 0;
end;
Keep in mind that this also blocks keyboard up/down events. If you also wish to block keyboard left/right events, then you can also do this:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT] then
Key:= 0;
end;

Mouse over stringgrid when dragging?

Parts of my stringgrid are eligible drop targets, and some are not (first row is column headings, first column is a sort of index and subsequent columns may be dropped to). I have that coded and working.
Now I am thinking that it might be nice to gve a visual indiation to the user as he drags the mouse over a cell which is a potential drop target. I woudl like to highlight the first cell in the row and column of the cell over which he is currently hovering (or possibly the entire row and column, forming a sort of crosshair; I am as yet undecided). I reckon I can code that in OnDrawCell.
I had thought to use OnMouseMove and cehck if Dragging then, but ...
My problem is that when I am dragging the OnMouseMove event never gets called.
Is there any other way to know when the cursor is hovering over a strigngrid during a drag operation?
The OnDragOver event is specifically designed for doing this; it's called automatically, and provides the X and Y coordinates where the mouse pointer is located. There's a code sample available at that link location that demonstrates using it as well - it's for a TListBox, but the principle is the same.
procedure TForm1.FormCreate(Sender: TObject);
begin
ListBox1.Items.Add('Not');
ListBox1.Items.Add('In');
ListBox1.Items.Add('Alphabetical');
ListBox1.Items.Add('Order');
end;
// This OnDragOver event handler allows the list box to
// accept a dropped label.
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := Source is TLabel;
end;

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.

MouseWheelDown event fires multiple times in succession

Delphi 7 on Windows 7
I want to scroll down a list (TElTree) using the mouse wheel... so I start writing some code in the ElTree's OnMouseWheelDown event. Then I notice that it's scrolling 2 rows at once. The mouse wheel settings in Control Panel are set to scroll only 1 line at a time. If I place a breakpoint inside the event handler, I discover that the event handler is itself being executed twice in quick succession.... Why? How to ensure it only executes once (code please)?
When the wheel scrolls, the OS sends WM_MOUSEWHEEL messages. The high-order word of the wParam parameter indicates how far the wheel has turned. If it has turned one "click," then its value will be 120, or WHEEL_DELTA. But it might be less than that if your scroll wheel recognizes scrolling less than a click's worth.
Correct WM_MOUSEWHEEL message handlers need to consider that parameter and either keep a "scrolling accumulator" to keep track of how far the wheel has scrolled or else have the ability to scroll less than a full line. Message handlers that assume that each message signifies a full click of the wheel will be sorry.
Solution is quite simple: After calling the function you want to perform OnMouseWheel you must set Handled:=True so that the routine is not called multiple times. e.g.
procedure TMainForm.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
MousePos: TPoint; var Handled: Boolean);
begin
YourFunctionToExecute(Sender, Shift, MousePos, Handled);
Handled:=True;
end;

Custom drawing in TListview descendant

I have a descendant of TListView that offers some additional features, such as sorting and ability to load itself from a TDataset. I now wish to extend this component further so that certain aspects of the drawing can be specified at the time items are added to the list view.
I'm having trouble figuring out which procedure or procedures to override to take control of the drawing. I have overridden DrawItem to change the font style to include strikethrough and then call the inherited DrawItem. If I also specify the style LVS_OWNERDRAWFIXED (in an overriden CreateParams()) my function is called and works as I want except that only the item, and not the subitems, is drawn.
Does anyone know how I can tell the list view to draw the subitems also? I've found one example of a substantially enhanced list view, but this one isn't sufficiently well documented for me to follow exactly what's going on, and I'm hoping not to have to hook quite as many events and windows messages as this one does — it seems to me that simply setting the canvas pen, brush, and font and then having the item draw itself should not be quite so involved.
Here's what I have so far:
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure DrawItem(Item: TListItem; Rect: TRect; State: TOwnerDrawState); override;
procedure TLookupListView.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or lvs_OwnerDrawFixed;
end;
procedure TLookupListView.DrawItem(Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var I: Integer;
Left: Integer;
begin
Canvas.Font.Style := Canvas.Font.Style + [fsStrikeOut];
inherited DrawItem(Item, Rect, State);
//I know the canvas must be restored here, this is just for proof-of-concept.
end;
PLEASE NOTE: I'm not interested in doing custom drawing in a particular instance of TListView using the supplied events. I know how to do that. I'm trying to "bake in" this functionality to my custom TListview descendant component.
The component has a virtual method IsCustomDrawn() which is called to determine which code paths need to be executed. In the base class implementation it checks whether any of the event handlers to paint the subitems is assigned, but you can override the method to return True for all those paint stages you want handled, even when there is no event handler assigned.
If you want to handle everything in code you should probably override CustomDrawItem() and CustomDrawSubItem() and do everything there. To get everything working I would build the app with debug DCUs, step into from event handlers and look around what the minimum amount of code you can get away with is. The important method to check out is TCustomListView.CNNotify() - here the Windows messages for owner drawing are handled.
Edit:
I forgot to add that you should try to not owner draw the text in the control, but just to set canvas properties in the various paint stages - the reason being that otherwise you will have to make sure that text output is pixel-perfect in all Windows versions, something that the VCL doesn't achieve. You can see this by adding a few columns and rows to a list view and toggling the OwnerDraw property at design time, the text jumps around.
Which version of Delphi are you using? In Delphi 2007 TListView has support for custom-drawing by handling NM_CUSTOMDRAW messages, as described here. TListView already has events defined for custom-drawing subitems, as well as virtual methods you can override in your descendant.

Resources