Change chart values with mouse cursor in Delphi - delphi

I need to create a chart in Delphi 10, where the values of the Series can be changed with the mouse. I want to press a value of the chart with the mouse cursor and drag to change its value. Is there any property that needs to be enabled or does it have a specific chart component for it?
I saw another similar question, as shown by #KenWhite, but I did not understand it, because in that topic C# was used and the TeeChart component works differently in Delphi.
Can someone explain me how to use it in Delphi?
thanks

Simple example of dragging.
I've set chart AllowPanning to False to use right mouse button freely, line series, point style is circle with size=4, and seek for touched points with simple list traversal (don't sure whether Std has methods to get the nearest point to cursor).
Perhaps you would need some limitations (for example, limit horizontal shift by neighbor values etc)
DragIdx: integer = -1;
procedure TForm1.Button18Click(Sender: TObject);
var
i: Integer;
begin
for i := 0 to 19 do
Series1.AddXY(i, Sin(i/2));
end;
procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
i, xx, yy: Integer;
begin
if Button = mbRight then begin
DragIdx := -1;
for i := 0 to Series1.Count - 1 do begin
xx := Series1.CalcXPos(i);
yy := Series1.CalcYPos(i);
if Sqr(xx - x) + Sqr(yy - y) <= 5 * 5 then begin
DragIdx := i;
Break;
end;
end;
Memo1.Lines.Add(Format('grab %d', [DragIdx]));
end;
end;
procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
xx, yy: Double;
begin
if (ssRight in Shift) and (DragIdx >=0) then begin
Series1.GetCursorValues(xx, yy);
Memo1.Lines.Add(Format('change %d to %f %f', [DragIdx, xx, yy]));
Series1.XValues[DragIdx] := xx;
Series1.YValues[DragIdx] := yy;
Chart1.Repaint;
end;
end;

Related

TeeChart Series OnMouseEnter event

I am using the version of TeeChart that ships with Rad Studio XE3.
TeeChart provides a TChartSeries event which fires when the mouse pointer moves over a series line. I use this event to display the name of the series under the pointer.
The problem is, give a series line 1 pixel wide, it’s difficult to get the pointer exactly over the line. Is there some way to add ‘padding’ to the event so it fires X number of pixels to each side of the line?
Or is there some other way to accomplish this?
I'm adding a new property to Line (TLineSeries) and FastLine (TFastLineSeries) classes to accomplish this.
Series1.ClickTolerance := 4; // <-- number of pixels around mouse XY
The default value is zero (mouse XY should be exactly over the line), like the current behavior.
As a workaround, if you are using TLineSeries, pointers can be displayed at line point positions, and the internal "clicked" function will consider pointer size:
Series1.Pointer.Visible:=True;
And for more custom control, the code below is very similar to the internal code use to detect mouse clicks. The Tolerance constant specifies the number of extra pixels to consider "in the line".
procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
const
Tolerance=4;
var Clicked,
t : Integer;
Position,
P,Old : TPoint;
begin
Clicked:= -1;
Position.X:=X;
Position.Y:=Y;
for t:=Series1.FirstValueIndex to Series1.LastValueIndex do
begin
P.X:=Series1.CalcXPos(t);
P.Y:=Series1.CalcYPos(t);
if t>Series1.FirstValueIndex then
if PointInLine(Position,P.X,P.Y,Old.X,Old.Y,Tolerance) then
begin
Clicked:=t;
break;
end;
Old:=P;
end;
if Clicked = -1 then
Caption:=''
else
Caption:=IntToStr(Clicked);
end;
You can use the PointInLineTolerance function to check it at OnMouseMove event.
However, you have to loop the series points manually to transform the series values into pixels and pass them to this function.
uses Series;
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.View3D:=false;
for i:=0 to 5 do
Chart1.AddSeries(TLineSeries).FillSampleValues;
end;
procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var series, valueIndex: Integer;
P0, P1: TPoint;
begin
Chart1.Draw;
for series:=0 to Chart1.SeriesCount-1 do
with Chart1[series] do
for valueIndex:=FirstValueIndex to LastValueIndex-1 do
begin
P0.X:=CalcXPos(valueIndex);
P0.Y:=CalcYPos(valueIndex);
P1.X:=CalcXPos(valueIndex+1);
P1.Y:=CalcYPos(valueIndex+1);
if PointInLineTolerance(Point(X, Y), P0.X, P0.Y, P1.X, P1.Y, 5) then
begin
Chart1.Canvas.TextOut(X+5,Y-10,'Series ' + IntToStr(series));
exit;
end;
end;
end;

Move TRichEdit Caretpos

is there a way to change the caret position in pixel?
i would like to move the care pos everytime i move the mouse mouse.
like:
Onmousemove:
MoveCaretPos(X, Y);
No, you cannot set the position of the caret in a specific point, instead you must set the caret in a character position. To do this you must use the EM_CHARFROMPOS message to retrieve the closest character to a specified point and then set the value returned to the SelStart property.
Check this sample
procedure TForm1.RichEdit1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
APoint : TPoint;
Index : Integer;
begin
APoint := Point(X, Y);
Index := SendMessage(TRichEdit(Sender).Handle,EM_CHARFROMPOS, 0, Integer(#APoint));
if Index<0 then Exit;
TRichEdit(Sender).SelStart:=Index;
end;

How do I get another application's window handle passed to Delphi via a mouse click

How can I get the handle of a window to be passed to Delphi by the user selecting the window (could be any other aplication's window) by clicking with the mouse on it. In my Delphi app I could have a button the user clicks that starts this detection process as well as a label displaying the clicked on window's title in the Delphi app. When the user is satisfied he selected the correct window he could click the button in my Delphi app (which will be modal) to stop the selection process and let my app start doing to the other window what it needs to do...
if you know what text is in the title of the window, this code will do the trick for you:
var
WindowList: TList;
function GetHandle (windowtitle: string): HWND;
var
h, TopWindow: HWND;
Dest: array[0..80] of char;
i: integer;
s: string;
function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
begin
Result:= True;
WindowList.Add(Pointer(Handle));
end;
begin
result:= 0;
try
WindowList:= TList.Create;
TopWindow:= Application.Handle;
EnumWindows(#getWindows, Longint(#TopWindow));
i:= 0;
while (i < WindowList.Count) and (result = 0) do
begin
GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
s:= dest;
if length(s) > 0 then
begin
if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
begin
h:= HWND(WindowList[i]);
if IsWindow(h) then
result:= h
end
end;
inc(i)
end
finally
WindowList.Free;
end;
end;
Usage in your example (notepad puts the name of the opened file in the window caption):
h:= getHandle('text.txt');
if (h = 0)
// Oops not found
else
begin
// you got the handle!
end;
I used this code to check if my application was already up and running. But it can be used on any launched application.
The approach that user STATUS_ACCESS_DENIED outlined in the comment is likely the simplest way to go here. I'd recommend using mouse capture over hooking, as it's somewhat simpler to implement.
Here's a slightly more detailed outline of what's involved:
The first thing to change the way that the selection process works. Instead of having the user click a button on your app to start the process, and then click the target window, and finally click again to confirm; it's a lot easier to implement if you have the user click a specific area on your app, then drag to the target window, and then let go of the mouse button while over the target. This is because windows considers a click on another app to belong to that app, and you have to do extra work to intercept it. But there's a simple way - called mouse capture - to get information about a drag/release if it starts off as a click on your own app.
This is also the approach that the Windows SDK Spy++ tool uses; so by doing it this way, you're also being consistent with a well-known tool. (Pic of Spy++ here - note the crosshair Finder Tool in the dialog - that's what you click and drag to the target. Would highly recommend downloading the Windows SDK and playing with this tool if you haven't done so before; it's also a very useful way of seeing how other applications are constructed so great as a Windows API learning tool.)
Steps involved:
Have some control in your app that response to mouse-down events (WM_LBUTTONDOWN in Win32/C, OnMouseDown in delphi). You might want to draw a crosshairs icon or similar here so the user knows where to click.
When you get a mouse down, use SetCapture to 'capture' the mouse. This means that the control will receive all the mouse messages while the mouse is moving - until the user releases the button - even if it moves outside the control.
Set the icon to look like a crosshairs so that the user knows they are in dragging mode
As the user moves the mouse, you'll get WM_MOUSEMOVE message (OnMouseMove in Delphi) that has the pointer coordinates. You'll need to use ClientToScreen to convert these to screen coordinates, then WindowFromPoint to find the window at that point. (Note that this finds the innermost window at that point, you could use ChildWindowFromPoint starting from the desktop window to just get the top-level window if you want that.) It's up to you to decide whether you want to update your UI at every mouse move throughout the drag, or just when the user releases the mouse button.
When the user releases the mouse button, you'll get a WM_LBUTTONUP/OnMouseUp; at that stage, wrap things up by calling ReleaseCapture and putting the cursor back to normal shape.
Note that you'll get mouse move events both during the drag, and also if the user just happens to move the mouse pointer across your control, perhaps on the way to some other control. The simplest way to tell these two cases apart is to use a flag in your control that you set when you get the mouse down, and clear when you get the mouse up, and only process mouse move events if that flag is set.
The above describes the process in terms of plain Win32 APIs that you'd call from C/C++; but it looks like Delphi provides direct support for most or all of them.
edit: Possible Delphi implementation:
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FCacheWnd: HWND;
FCaptured: Boolean;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const // the first item, the place where the crosshair is
ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);
procedure TForm1.FormPaint(Sender: TObject);
begin
// draw the control and the crosshair if no capturing
if GetCapture <> Handle then begin
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
Screen.Cursors[crCross]);
end;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (Shift = [ssLeft])
and PtInRect(ClickRect, Point(X, Y)) then begin
// the second item, draw the control pressed,
// set the flag and the capture. FCacheWnd is used not to get
// window information for every mouse move - if the window under the
// mouse is not changed.
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
FCacheWnd := 0;
FCaptured := True;
SetCapture(Handle);
Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
end;
end;
function GetWndFromClientPoint(ClientWnd: HWND; Pt: TPoint): HWND;
begin
MapWindowPoints(ClientWnd, GetDesktopWindow, Pt, 1);
Result := WindowFromPoint(Pt);
end;
function GetWndInfo(Wnd: HWND): string;
var
ClassName: array [0..256] of Char;
begin
Result := '';
if IsWindow(Wnd) then begin
GetClassName(Wnd, ClassName, 256);
Result := Format('Window: %x [%s]', [Wnd, ClassName]);
if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
Wnd := GetAncestor(Wnd, GA_ROOT);
GetClassName(Wnd, ClassName, 256);
Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
end;
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
Wnd: HWND;
begin
if FCaptured then begin
// fourth item, convert coordinates and find the window under the cursor
Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
if Wnd <> FCacheWnd then
Label1.Caption := GetWndInfo(Wnd);
FCacheWnd := Wnd;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FCaptured then begin
// fifth item
FCaptured := False;
ReleaseCapture;
InvalidateRect(Handle, #ClickRect, False); // invalidate pressed look
Screen.Cursor := crDefault;
end;
end;
Edit: It's gone, but you used to be able to download Delphi Window Spy by Eddie Shipman, from delphipages.com, which has turned into a festering heap of useless linkbait.

How to launch an external application in Delphi and get the window handle of the newly started app? [duplicate]

How can I get the handle of a window to be passed to Delphi by the user selecting the window (could be any other aplication's window) by clicking with the mouse on it. In my Delphi app I could have a button the user clicks that starts this detection process as well as a label displaying the clicked on window's title in the Delphi app. When the user is satisfied he selected the correct window he could click the button in my Delphi app (which will be modal) to stop the selection process and let my app start doing to the other window what it needs to do...
if you know what text is in the title of the window, this code will do the trick for you:
var
WindowList: TList;
function GetHandle (windowtitle: string): HWND;
var
h, TopWindow: HWND;
Dest: array[0..80] of char;
i: integer;
s: string;
function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
begin
Result:= True;
WindowList.Add(Pointer(Handle));
end;
begin
result:= 0;
try
WindowList:= TList.Create;
TopWindow:= Application.Handle;
EnumWindows(#getWindows, Longint(#TopWindow));
i:= 0;
while (i < WindowList.Count) and (result = 0) do
begin
GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
s:= dest;
if length(s) > 0 then
begin
if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
begin
h:= HWND(WindowList[i]);
if IsWindow(h) then
result:= h
end
end;
inc(i)
end
finally
WindowList.Free;
end;
end;
Usage in your example (notepad puts the name of the opened file in the window caption):
h:= getHandle('text.txt');
if (h = 0)
// Oops not found
else
begin
// you got the handle!
end;
I used this code to check if my application was already up and running. But it can be used on any launched application.
The approach that user STATUS_ACCESS_DENIED outlined in the comment is likely the simplest way to go here. I'd recommend using mouse capture over hooking, as it's somewhat simpler to implement.
Here's a slightly more detailed outline of what's involved:
The first thing to change the way that the selection process works. Instead of having the user click a button on your app to start the process, and then click the target window, and finally click again to confirm; it's a lot easier to implement if you have the user click a specific area on your app, then drag to the target window, and then let go of the mouse button while over the target. This is because windows considers a click on another app to belong to that app, and you have to do extra work to intercept it. But there's a simple way - called mouse capture - to get information about a drag/release if it starts off as a click on your own app.
This is also the approach that the Windows SDK Spy++ tool uses; so by doing it this way, you're also being consistent with a well-known tool. (Pic of Spy++ here - note the crosshair Finder Tool in the dialog - that's what you click and drag to the target. Would highly recommend downloading the Windows SDK and playing with this tool if you haven't done so before; it's also a very useful way of seeing how other applications are constructed so great as a Windows API learning tool.)
Steps involved:
Have some control in your app that response to mouse-down events (WM_LBUTTONDOWN in Win32/C, OnMouseDown in delphi). You might want to draw a crosshairs icon or similar here so the user knows where to click.
When you get a mouse down, use SetCapture to 'capture' the mouse. This means that the control will receive all the mouse messages while the mouse is moving - until the user releases the button - even if it moves outside the control.
Set the icon to look like a crosshairs so that the user knows they are in dragging mode
As the user moves the mouse, you'll get WM_MOUSEMOVE message (OnMouseMove in Delphi) that has the pointer coordinates. You'll need to use ClientToScreen to convert these to screen coordinates, then WindowFromPoint to find the window at that point. (Note that this finds the innermost window at that point, you could use ChildWindowFromPoint starting from the desktop window to just get the top-level window if you want that.) It's up to you to decide whether you want to update your UI at every mouse move throughout the drag, or just when the user releases the mouse button.
When the user releases the mouse button, you'll get a WM_LBUTTONUP/OnMouseUp; at that stage, wrap things up by calling ReleaseCapture and putting the cursor back to normal shape.
Note that you'll get mouse move events both during the drag, and also if the user just happens to move the mouse pointer across your control, perhaps on the way to some other control. The simplest way to tell these two cases apart is to use a flag in your control that you set when you get the mouse down, and clear when you get the mouse up, and only process mouse move events if that flag is set.
The above describes the process in terms of plain Win32 APIs that you'd call from C/C++; but it looks like Delphi provides direct support for most or all of them.
edit: Possible Delphi implementation:
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FCacheWnd: HWND;
FCaptured: Boolean;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const // the first item, the place where the crosshair is
ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);
procedure TForm1.FormPaint(Sender: TObject);
begin
// draw the control and the crosshair if no capturing
if GetCapture <> Handle then begin
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
Screen.Cursors[crCross]);
end;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (Shift = [ssLeft])
and PtInRect(ClickRect, Point(X, Y)) then begin
// the second item, draw the control pressed,
// set the flag and the capture. FCacheWnd is used not to get
// window information for every mouse move - if the window under the
// mouse is not changed.
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
FCacheWnd := 0;
FCaptured := True;
SetCapture(Handle);
Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
end;
end;
function GetWndFromClientPoint(ClientWnd: HWND; Pt: TPoint): HWND;
begin
MapWindowPoints(ClientWnd, GetDesktopWindow, Pt, 1);
Result := WindowFromPoint(Pt);
end;
function GetWndInfo(Wnd: HWND): string;
var
ClassName: array [0..256] of Char;
begin
Result := '';
if IsWindow(Wnd) then begin
GetClassName(Wnd, ClassName, 256);
Result := Format('Window: %x [%s]', [Wnd, ClassName]);
if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
Wnd := GetAncestor(Wnd, GA_ROOT);
GetClassName(Wnd, ClassName, 256);
Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
end;
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
Wnd: HWND;
begin
if FCaptured then begin
// fourth item, convert coordinates and find the window under the cursor
Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
if Wnd <> FCacheWnd then
Label1.Caption := GetWndInfo(Wnd);
FCacheWnd := Wnd;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FCaptured then begin
// fifth item
FCaptured := False;
ReleaseCapture;
InvalidateRect(Handle, #ClickRect, False); // invalidate pressed look
Screen.Cursor := crDefault;
end;
end;
Edit: It's gone, but you used to be able to download Delphi Window Spy by Eddie Shipman, from delphipages.com, which has turned into a festering heap of useless linkbait.

ListBox long items hints

There's a ListBox with some long items. These long items are getting beyond the right edge of the ListBox and here comes an idea to show hints for such items when the mouse is over them.
I've found an example: (from http://delphi.about.com/cs/adptips2001/a/bltip0201_4.htm)
procedure TForm1.ListBox1MouseMove (Sender: TObject; Shift: TShiftState; X, Y: Integer) ;
var lstIndex : Integer ;
begin
with ListBox1 do
begin
lstIndex:=SendMessage(Handle, LB_ITEMFROMPOINT, 0, MakeLParam(x,y)) ;
if (lstIndex >= 0) and (lstIndex <= Items.Count) then
Hint := Items[lstIndex]
else
Hint := ''
end;
end;
It works, but every time I want to view a hint for another item I have to move my mouse away from the ListBox and then point on another item to see its hint. Is there any way to view hints for every item without moving the mouse away from the ListBox borders?
var fOldIndex: integer = -1;
procedure TForm1.ListBox1MouseMove (Sender: TObject; Shift: TShiftState; X, Y: Integer) ;
var lstIndex : Integer ;
begin
with ListBox1 do
begin
lstIndex:=SendMessage(Handle, LB_ITEMFROMPOINT, 0, MakeLParam(x,y)) ;
// this should do the trick..
if fOldIndex <> lstIndex then
Application.CancelHint;
fOldIndex := lstIndex;
if (lstIndex >= 0) and (lstIndex <= Items.Count) then
Hint := Items[lstIndex]
else
Hint := ''
end;
end;

Resources