Specifically I want a handle to an embedded chart window so that I can run
some API functions in a VBA procedure.
I'm beginning to suspect that it isn't possible to obtain a handle to any
window below the main window of a workbook. Certainly running a "Window
Title and Class Name Demo" VB program (from
http://www.mvps.org/vbnet/Win32) within Excel failed to return any lower
windows with obvious names or classes.
However my knowledge of the API is very basic - does anyone know any
better?
Thanks
David
You have now posted your request multiple times to multiple
groups over a few weeks - with apparently no satisifactory answer.
Perhaps it would be worthwhile paying MS for a support incident
and requesting the class name for an Excel chart. Using EXCEL.CHART
and EXCEL.CHART.8 did not give me any results with finding the window
handle using the FindWindowLike function from VB.net.
Good luck,
Sam
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10f8d$b64fc110$d00a5092@DAVID_LEE...
http://208.49.24.208/Excel/Default.htm
Regards,
Tom Ogilvy
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10f8d$b64fc110$d00a5092@DAVID_LEE...
I think you are being somewhat harsh in your implied criticism! I
initially posted a query in someone else's thread and so it may well have
been missed by many people. I reposted a request on this NG, to which you
kindly replied with some suggestions, but the title of the posting did not
clearly state my specific problem - hence the second ("multiple!") attempt
in the hope that someone in the know may actually read it.
As for the other news group of my "multiple" of TWO (m.p.e.sdk); it appears
on the basis of it's name to be a more appropriate forum for my request but
doesn't seem to see as much activity.
Paying MS for a support incident is not a possibility (and I'm NOT dipping
into my own salary to do it!). I'm sure that someone amongst the excellent
professionals active on this group MUST know whether or not what I am
trying to do is at least possible. A reply telling me that they think it's
impossible would have saved me much wasted time.
***************************************
Anyway, whereas the VBA property:
ActiveWindow.Caption
accessed from a mouse event in an embedded chart returns
[Book1]Sheet1 Chart1
as expected, running the API functions
GetWindowText(GetActiveWindow, strCaption, lngLen)
returns
Microsoft Excel - Book1
even when the chart has been activated as a Chart Window - complete with
title bar containing "[Book1]Sheet1 Chart1"!
So whilst Excel "knows" that an embedded chart is a separate window it
looks as though it is not accessible to the API.
I would still appreciate confirmation of this though!
And thanks for your earlier suggestions - whilst they didn't help to sove
my problem the links certainly improved my knowledge of VB/VBA!
David
S...@internet.org wrote in article <uBzfEc5DBHA.1352@tkmsftngp05>...
Anyway, using Stephen Bullen's Enumdlg.xls file, with an embedded
chart in a chart window (right click chart and select the Chart Window
option) and without the window, it appears that the class name for the
chart is EXCELE.
It would be interesting to know whether this now allows you to solve
your original problem with client coordinates successfully.
-- Sam
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10faa$7b231c00$d00a5092@DAVID_LEE...
I am rather surprised that you have not received an answer to this
question from the Microsoft MVP - Excel types.
The class name for an embedded chart is EXCELE.
'Get the handle on Excel's main window
hwnd = FindWindow("XLMAIN", Application.Caption)
'Get the handle on Excel's desktop
hWndDesk = FindWindowEx(hwnd, 0, "XLDESK", vbNullString)
'Get the handle for an embedded chart
hWndChart = FindWindowEx(hWndDesk, 0, "EXCELE", vbNullString)
You should be able to then call GetClientRect with hWndChart as
the window handle:
Using your original code:
err = GetClientRect(hWndChart,winRect)
In your original posting, you declared the user defined data type
RECT as:
Private Type RECT
Left As Integer
Top As Integer
Right As Integer
Bottom As Integer
End Type
This is incorrect. The data types must be Long not Integer.
Unfortunately, I do not think this is going to help you with
translating the client coordinates as returned from a MouseDown event
into "Chart coordinates". What you really need is the coordinates of
the plot area (specifically left top, left bottom, right bottom in
client coordinates). You were probably thinking you could ratio the
height and width of the chart object and use that relationship to
convert the Plot Area coordinates to client coordinates. It does not
work.
I setup a sample worksheet where I defined a chart, enabled chart
events, and using a crosshair cursor for precision, extracted various
client coordinates from the chart. The inside top property was defined
as 19 points. The same location in client coordinates is 30 pixels. If
you try and calculate this value you get about 25 pixels using the ratio
of object height in points to object height in client coordinates.
What we need is a coordinates transformation function which will work
no matter what the current scale or scroll position. Whether this
is possible given what we have to work with is still unknown.
These are just my thoughts and I am by no means an expert. Hopefully
we can figure this out once and for all.
John Chauvin
As you know, a few of us have told you that the class name for an embedded
chart is EXCELE. I just wanted to let you know how I found that info (for
your benefit and the group's). I used an app called Spy++, which is
included with Visual Studio 6.0. It is very handy for finding windows'
class names and other information like that. Anyway, if you have access to
VS6, check it out....
Regards,
Jake Marx
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10f8d$b64fc110$d00a5092@DAVID_LEE...
There is no window with class EXCELE on my machine at all. However using
Spy++ (as suggested by Jake) I was able to identify the active chart window
as a child of XLDESK with the class name EXCELC (the same as for a chart
sheet).
(I'm using XL97 - is there perhaps a difference in classe names between 97
and XL2000).
Unfortunately having now obtained a handle to the window, I have run into
similar problems to John Chauvin although I had thought that I had a better
way of transforming the coordinates.
I transform the X,Y (client) coordinates returned by MouseDown into points
using the window sizes returned by GetClientRect and ActiveWindow.Left,
.Top, .Width and .Height. I then map these onto my axis coordinates using
the length and positions of the category and value axes to define the plot
area. (category .Left & .Width and value .Top & .Height)
Whilst the coordinates returned by this means are close to the expected
values there are small but significant errors of the order of one or two
percent at the axis minima and maxima.
For x and y axes in the range 0 to 10, I obtained coordinates of (+0.112,
-0137) for the origin (0, 0) and (+10.093, +9.795) for x,y max (10, 10).
Repeating the excercise but deriving the points window size from the size
of the chart object (ActiveChart.Parent) gave the same results within
reasonable limits of error. Using dimensions from ActiveChart.Chart Area
gave slightly different results: (+0.102, +0.005) & (+10.093, +9.795).
It seems that Excel and the API have slightly different ideas about where
the chart window actually is!
The other possibility of calibration that occurs to me is to move the
cursor to known positions on the chart (ends of the axes?) using
SetCursorPos and fire the MouseDown event using mouse_event (both API
functions from User32.dll). Then at least we would know that Excel and the
API were looking at the same pair of points.
Unfortunately I don't know how to convert the points coordinates derived
from the XL Axis properties into screen coordinates. I just seem to have
gone round in a circle (and I still don't understand why GetActiveWindow
doesn't work for windows inside XL)!
Cheers all
David
Also, using Stephen's enumdlg routine, no EXCELE is produced (in my tests).
Anyway, that is the way it appeared to me. Maybe it is a known fact that
the embedded chart class is EXCELE, but it doesn't appear to be to me. Any
thoughts?
Regards,
Tom Ogilvy
Jake Marx <ja...@longhead.com> wrote in message
news:OudAAh7DBHA.704@tkmsftngp07...
Now that I look into it more, I am confused as to what EXCELE represents.
If I resize the chart border, the window size in Spy++ changes along with
it. However, if I resize the chart itself, Spy++ doesn't mirror the
changes. And you're right, EXCELE doesn't seem to go away when you remove
the chart(s).
I guess I don't know enough about the Windows API to figure out exactly what
an embedded chart is. I figured it was a child window of XLDESK, which is a
child of XLMAIN, but I don't know if that is true (it doesn't seem to be).
> Anyway, that is the way it appeared to me. Maybe it is a known fact that
> the embedded chart class is EXCELE, but it doesn't appear to be to me.
Any
> thoughts?
No, I don't think it's a known fact - I was just coming up with my best
guess. Searches on "excele" and several related queries turned up nothing.
Maybe the OP will figure out the solution and let us all know.
Regards,
Jake Marx
"Tom Ogilvy" <twog...@email.msn.com> wrote in message
news:uAUdGw$DBHA.1412@tkmsftngp02...
In my earlier reply I should have been specific that EXCELE
appears to be the class that something to do with embedded
charts on XL2000.
However, as noted by TomOgilvy in his second post, only one
EXCELE window is reported regardless of the actual number
of embedded charts, and a window caption is returned only
when any object on a chart (and, therefore, the chart) is activated.
Also, I observed that EXCELE persists and is returned even after
all charts have been deleted and the file saved. EXCELE is not
reported only after the Excel application has been shut down, and
the (now) chart-less file reopened.
Note: EXCELC does not return any handles in XL2000 for
embedded charts.
Other things to consider:
1) Mouse sensitivity settings may affect the position returned, and
this may vary dpending upon the base units (twip, pixel, or point)
used by the object.
2) Whatever the screen display resolution that has been set
(640x480, 1024 x 768 etc), screen coordinates are oriented from
the top right (0,0) to the bottom left (65536,65536 always) of the
display.
--Sam
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10fe4$6ac833b0$d00a5092@DAVID_LEE...
You may find the following routine useful:
Option Explicit
Type POINT
x As Long
y As Long
End Type
Private Declare Sub GetCursorPos Lib "user32" (lpPoint As CURSOR)
Sub CursorPos()
Dim location As POINT
' Retrieve mouse coordinates
' Values returned in the range (0,0)
' to (display resolution X, display resolution Y)
Call GetCursorPos(location)
ActiveCell.Value = "Current X = " & location.x & Chr(10) & "Current Y = " & location.y
End Sub
Also, the API function "ClientToScreen" converts client coordinates to
screen coordinates.
Private Declare Sub ClientToScreen Lib "user32" _
(ByVal hWnd As Long, _
lpPoint As POINT)
-- Sam
"David Lee" <dlee_m...@hotmail.com> wrote in message
news:01c10fe4$6ac833b0$d00a5092@DAVID_LEE...
Change the line:
Private Declare Sub GetCursorPos Lib "user32" (lpPoint As CURSOR)
to
Private Declare Sub GetCursorPos Lib "user32" (lpPoint As POINT)
POINT is standard usage in the MSKB articles.
--Sam
<S...@internet.org> wrote in message news:OsZ9etFEBHA.696@tkmsftngp02...
> Now that I look into it more, I am confused as to what EXCELE represents.
It seems to me to be the editing window that Excel uses when an embedded
chart is activated (which may be exactly what Dave wants!). It provides
the drag handles for the chart, and the thick border and caption if you've
chosen the 'View in own window' option.
To find the EXCELE handle, it's easiest to drill down from XLMAIN, using
FindWindowEx:
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Sub test()
Dim hWndXL As Long, hWndDesk As Long, hWndXLE As Long
hWndXL = FindWindow("XLMAIN", Application.Caption)
hWndDesk = FindWindowEx(hWndXL, 0&, "XLDESK", vbNullString)
hWndXLE = FindWindowEx(hWndDesk, 0&, "EXCELE", vbNullString)
End Sub
Regards
Stephen Bullen
Microsoft MVP - Excel
http://www.BMSLtd.co.uk or http://208.49.24.208
Sorry that I haven't replied sooner, but I've been away from the groups for
a while...
> .... to obtain a handle to a window within an Excel worksheet?
Yes, if the window exists, but not if the worksheet is controlling the
drawing of the object that appears to be in its own window. For example, an
embedded chart object shown on a sheet does not have its own window, but a
TreeView control on a sheet does.
> Specifically I want a handle to an embedded chart window so that I can run
> some API functions in a VBA procedure.
Embedded charts don't have their own window, though they get an editing
window when activated, of class type EXCELE (in XL2000), which appears to
provide the grab handles to resize the embedded chart and the Window title
etc when viewing the chart in its own window.
It appears, though, that you're trying to convert the X, Y coordinates given
in the chart Mouse events to numerical coordinates (e.g. the coordinates of
the data being displayed in the chart's plot area).
This conversion depends on a number of factors, as you've already
discovered:
- The worksheet zoom factor (impacting the conversion of pixels to points)
- Whether showing large or small fonts (impacting the conversion of pixels
to points)
- The differing coordinate systems of the X,Y numbers and the chart item's
dimensions 0,0 is top-left in mouse coords but bottom-left in chart coords.
- The differing origins of the coordinate systems. 0,0 in mouse coords is
the top-left of the chart area (a few pixels inside the window), while (0,0)
in chart coords is the bottom-left of the inside of the plot area.
Given the above, the following seems to provide +/- 1 pixel accuracy in
most settings I've tested it in (hope the word wrapping is OK):
'API's for getting the factors to convert points to pixels
Private Declare Function GetDC Lib "user32" ( _
ByVal hwnd As Long) As Long
Private Declare Function GetDeviceCaps Lib "Gdi32" ( _
ByVal hDC As Long, _
ByVal nIndex As Long) As Long
Private Declare Function ReleaseDC Lib "user32" ( _
ByVal hwnd As Long, _
ByVal hDC As Long) As Long
Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90
Public WithEvents oChart As Chart
Private Sub oChart_MouseMove(ByVal Button As Long, ByVal Shift As Long,
ByVal X As Long, ByVal Y As Long)
Dim dZoom As Double
Dim dMin As Double
Dim dMax As Double
dZoom = ActiveWindow.Zoom / 100
With oChart
dMin = .Axes(xlCategory).MinimumScale
dMax = .Axes(xlCategory).MaximumScale
xVal = dMin + (dMax - dMin) * ((X - IIf(dZoom > 1, 6 * (dZoom - 1),
0)) * PointsPerPixelX / dZoom - _
(.PlotArea.InsideLeft +
.ChartArea.Left)) / .PlotArea.InsideWidth
dMin = .Axes(xlValue).MinimumScale
dMax = .Axes(xlValue).MaximumScale
yVal = dMin + (dMax - dMin) * (1 - ((Y - IIf(dZoom > 1, 6 * (dZoom -
1), 0)) * PointsPerPixelY / dZoom - _
(.PlotArea.InsideTop +
.ChartArea.Top)) / .PlotArea.InsideHeight)
End With
Application.StatusBar = "(" & Application.Round(xVal, 2) & ", " &
Application.Round(yVal, 2) & ")"
End Sub
'The width of a pixel in Excel's userform coordinates
Public Property Get PointsPerPixelX() As Double
Dim hDC As Long
hDC = GetDC(0)
'A point is defined as 1/72 of an inch and LOGPIXELSX returns
'the number of pixels per logical inch, so divide them to give
'the width of a pixel in Excel's userform coordinates
PointsPerPixelX = 72 / GetDeviceCaps(hDC, LOGPIXELSX)
ReleaseDC 0, hDC
End Property
'The width of a pixel in Excel's userform coordinates
Public Property Get PointsPerPixelY() As Double
Dim hDC As Long
hDC = GetDC(0)
'A point is defined as 1/72 of an inch and LOGPIXELSX returns
'the number of pixels per logical inch, so divide them to give
'the width of a pixel in Excel's userform coordinates
PointsPerPixelY = 72 / GetDeviceCaps(hDC, LOGPIXELSY)
ReleaseDC 0, hDC
End Property
How did you come up with the constants LOGPIXELSX and LOGPIXELSY?
Can you explain exactly what the theory is behind the equations for
xVal and yVal?
Thanks again for your help.
John Chauvin
In article <VA.0000086...@bmsltd.ie>,
> The only problem I have noticed is the Y coordinate becomes
> extremely large if the chart is partially scrolled out of view.
Left, right, top or bottom?
> I do have a
> few questions:
>
> How did you come up with the constants LOGPIXELSX and LOGPIXELSY?
They're defined in the 'Windows API Viewer' tool that comes with the
Developer editions of Office and VB etc.
> Can you explain exactly what the theory is behind the equations for
> xVal and yVal?
Sure:
The X and Y values coming in to the event are pixel coordinates, with an
origin at the top-left of the ChartArea.
The data plotted on that chart are in coordinates defined by the Category and
Value axes (assuming an XY chart), where the bit displayed on the chart is
the area between the axes minimum and maximum values and where the origin is
the bottom-left corner of the inside of the plot area (unless the 'values in
reverse order' check box is ticked on either axis, in which the origin could
be in any corner - but I ignored that complexity!)
To convert from one coordinate system (pixels on the screen) to another (data
in the chart), we first need to decide on a common measurement system. I
chose to use Points, as that is the measurement system used by the chart
objects (ChartArea, PlotArea) etc.
So, the conversion goes something like this:
1. We're given the cursor position in pixels, X
2. Convert that to points, using the PointsPerPixel functions, adjusting for
different display Zoom factors, (X * PointerPerPixelX / dZoom)
3. At >100% zoom, the result seemed to drift a little, so I added an
adjustment to counteract that, IIf(dZoom > 1, 6 * (dZoom - 1)) *
PointsPerPixel / dZoom
NB. This may not be completely accurate, but seems OK in testing
4. I now have the cursor position in points.
5. Subtract the PlotArea's InsideLeft amount and the ChartArea's Left amount
to find the gap between the left of the inside plot area and the cursor
6. Take the ratio of that gap to the PlotArea's InsideWidth and apply it to
the visible range of the axes scaling.
7. That gives me the data-coordinate value for the cursor's position.
John Chauvin
Initialize the chart, scroll the embedded chart so that the top is
not visible in on the Excel desktop. Select the chart, moving the
mouse shows a large Y coordinate in the status bar display.
A similar phenomenon occurs if the left side of the chart is
scrolled off resulting in a large negative X coordinate value. Both
are of the form 114532####.## on a 1024x768 display XL2000,
on Win98SE.
The readings are normal when the right and/or the bottom of the
chart is not visible due to scrolling.
-- Sam
Thanks
It looks like there's a bug in .ChartArea.Left and .ChartArea.Top -
they're probably using unsigned longs behind the scenes. We just need
to detect it and change it back (hope the word-wrap's OK):
Private Sub oChart_MouseMove(ByVal Button As Long, ByVal Shift As Long,
ByVal X As Long, ByVal Y As Long)
Dim dZoom As Double
Dim dMin As Double
Dim dMax As Double
Dim dChartLeft As Double
Dim dChartTop As Double
dZoom = ActiveWindow.Zoom / 100
With oChart
dChartLeft = .ChartArea.Left
If dChartLeft > 2 ^ 31 Then
dChartLeft = dChartLeft - 2 ^ 32
End If
dChartTop = .ChartArea.Top
If dChartTop > 2 ^ 31 Then
dChartTop = dChartTop - 2 ^ 32
End If
dMin = .Axes(xlCategory).MinimumScale
dMax = .Axes(xlCategory).MaximumScale
xVal = dMin + (dMax - dMin) * ((X - IIf(dZoom > 1, 6 * (dZoom -
1), 0)) * PointsPerPixelX / dZoom - _
(.PlotArea.InsideLeft +
dChartLeft)) / .PlotArea.InsideWidth
dMin = .Axes(xlValue).MinimumScale
dMax = .Axes(xlValue).MaximumScale
yVal = dMin + (dMax - dMin) * (1 - ((Y - IIf(dZoom > 1, 6 *
(dZoom - 1), 0)) * PointsPerPixelY / dZoom - _
(.PlotArea.InsideTop +
dChartTop)) / .PlotArea.InsideHeight)
End With
Application.StatusBar = "(" & Application.Round(xVal, 2) & ", " &
Application.Round(yVal, 2) & ")"
End Sub
--Sam
"Stephen Bullen" <Ste...@BMSLtd.ie> wrote in message
news:VA.0000086...@bmsltd.ie...
Thanks again for your help. I have been scratching my head trying to
figure the flaw in my original logic. I traced it to a
misunderstanding on the difference between chart object and chart
area. I assume they were the same thing. Obviously, upon closer
examination, they are not.
One side comment for all you Excel authors out there, it would be very
helpful to include a detailed diagram of a sample chart with all the
various objects and dimensions. I am not talking just ChartArea and
PlotArea but all the various properties like insideWidth and
insideHeight. It is not always clear exactly what these properties
really mean.
Anyway,when I examined the ChartArea Top and Left, they had a small
but finite value. There small value indicates that they are not
measure from the top of row 1 on the worksheet which is what I think
the ChartObject top property refers. So that begs the question what
exact does the chartArea top refer to?
Here is what is confusing me. If I enable chart events, and examine
the client coordinates for a simple XY embedded chart by clicking on
the chart area border, I get X = 0 and Y = 0 so the client coordinates
are measured from the visible chart area border. So the gap in points
between the chart area and plot area should be insideTop/insideLeft.
Yet this does not work, for example, you have to take the sum of the
ChartTop plus PlotAreaInsideTop to get a compariable measure to that
represented by the client coordinates. Why
What am I missing?
Thanks,
John Chauvin
> Yet this does not work, for example, you have to take the sum of the
> ChartTop plus PlotAreaInsideTop to get a compariable measure to that
> represented by the client coordinates. Why
When you draw a chart on a worksheet, you get a ChartObject, which has
Top and Left properties based on the worksheet (0,0 is top-left of cell
A1).
Inside a ChartObject there is a small border, then the ChartArea, so
ChartArea.Top and .Left are 12 points or so - the size of that border.
Inside the ChartArea is the PlotArea (which includes the axis labels),
inside of which is the areae actually drawn on. 0,0 of the InsideLeft
and InsideTop is the top-left of the ChartArea, while 0,0 of the X and
Y values given in the charts mouse events is the top-left of the
ChartObject (view in a fixed-width font):
1------------------------------------------+
|ChartObject |
| 2--------------------------------------+ |
| |ChartArea | |
| | | |
| | +--------------------------------+ | |
| | |PlotArea | | |
| | | 20 +-----------------------+| | |
| | | | 'Inside' dimensions || | |
| | | 10 | || | |
| | | | || | |
| | | 0 +-----------------------+| | |
| | +--------------------------------+ | |
| +--------------------------------------+ |
+------------------------------------------+
Point 1 is the origin for X and Y values passed to chart mouse events.
Point 2 is the original for the PlotArea InsideLeft and InsideTop.
The difference is the ChartArea.Left and ChartArea.Top
So when you create a standard XY chart, what object is enclosed by the
outside border...the chart area or chart object? Again using the Chart
MouseDown event and GetChartElement, it appears that the visible object
is the chart area (i.e. if I click one pixel below the border I get
xlChartArea.). But that cannot be true since the client coordinates
returned by the MouseDown event is measured from the Chart Object and
when I click on the chart border I get X=0, Y=0.
John Chauvin
In article <VA.0000087...@bmsltd.ie>,
Excellent! Thanks very much for your solution.
Is your adjustment IIf(dZoom > 1, 6 * (dZoom - 1)) * PointsPerPixel / dZoom
based on trial and error or some calculation? On my machine (running
Office 97 under NT4 SP6) the accuracy is not quite as good as you found.
Using the Accessibility Options to nudge a cross hairs cursor I find that
the values returned can be off by up to 4 mouse increments and I wonder if
fine tuning may further improve this? Even so the accuracy and stability
are very much better than I was getting using my solution (using
GetClientRect to calibrate the x,y coordinates).
Now you've shown us what to look for I've come across a demo at
http://www.mvps.org/vbnet/index.html?code/screen/displaysettings.htm
that includes a listing of the values of the constants used in
GetDeviceCaps. Could be helpful if anyone wants to experiment further.
Many thanks for your time.
David
> Is your adjustment IIf(dZoom > 1, 6 * (dZoom - 1)) * PointsPerPixel / dZoom
> based on trial and error or some calculation?
100% Trial and error, and not too much of it either <g>