I am trying to write a Macro that pastes a shape at the point position instead of the default ppt behavior which is to paste it next to the copied object.
I assigned a keyboard shortcut to the Get_Cursor_Pos macro which saves the current point position and then I try to paste it with the Paste macro.
However, the latter pastes it in a different position than the saved cursor position. I suspect this is due to different positioning units being used in both macros. How can I fix this issue?
' Access the GetCursorPos function in user32.dll
Declare Function GetCursorPos Lib "user32" _
(lpPoint As POINTAPI) As Long
' Access the GetCursorPos function in user32.dll
Declare Function SetCursorPos Lib "user32" _
(ByVal x As Long, ByVal y As Long) As Long
' GetCursorPos requires a variable declared as a custom data type
' that will hold two integers, one for x value and one for y value
Type POINTAPI
X_Pos As Long
Y_Pos As Long
End Type
' Dimension the variable that will hold the x and y cursor positions
Dim Hold As POINTAPI
' Main routine to dimension variables, retrieve cursor position,
' and display coordinates
Sub Get_Cursor_Pos()
' Place the cursor positions in variable Hold
GetCursorPos Hold
End Sub
Sub Paste()
ActivePresentation.Slides(1).Shapes.Paste
With ActiveWindow.Selection.ShapeRange
.Left = Hold.X_Pos
.Top = Hold.Y_Pos
End With
End Sub
------ EDIT -------
For the sake of helping other people with the same problem here is a solution incorporating Shyam and Steve's answers below. Since PPT doesn't allow you to assign a shortcut key to a macro (unless you use a paid add-in) I had to create an add-in with a toolbar as described here http://www.pptfaq.com/FAQ00031_Create_an_ADD-IN_with_TOOLBARS_that_run_macros.htm.
Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
' Access the GetCursorPos function in user32.dll
Declare Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
' GetCursorPos requires a variable declared as a custom data type
' that will hold two integers, one for x value and one for y value
Type POINTAPI
X_Pos As Long
Y_Pos As Long
End Type
' Dimension the variable that will hold the x and y cursor positions
Dim Hold As POINTAPI
Function GetSlideOriginOnScreen(Window As DocumentWindow) As POINTAPI
Dim pt As POINTAPI
With Window
pt.X_Pos = .PointsToScreenPixelsX(0)
pt.Y_Pos = .PointsToScreenPixelsY(0)
End With
GetSlideOriginOnScreen = pt
End Function
Function ConvertPixelToPointX(x As Long) As Single
Const LOGPIXELSX = 88
Const POINTSPERINCH = 72
Dim hDC As Long
Dim sngX As Long
hDC = GetDC(0)
sngX = GetDeviceCaps(hDC, LOGPIXELSX)
Call ReleaseDC(0, hDC)
ConvertPixelToPointX = (x / sngX) * POINTSPERINCH
End Function
Function ConvertPixelToPointY(y As Long) As Single
Const LOGPIXELSY = 90
Const POINTSPERINCH = 72
Dim hDC As Long
Dim sngY As Long
hDC = GetDC(0)
sngY = GetDeviceCaps(hDC, LOGPIXELSY)
Call ReleaseDC(0, hDC)
ConvertPixelToPointY = (y / sngY) * POINTSPERINCH
End Function
Sub Auto_Open()
Dim oToolbar As CommandBar
Dim oButton As CommandBarButton
Dim MyToolbar As String
' Give the toolbar a name
MyToolbar = "Paste Tools"
On Error Resume Next
' so that it doesn't stop on the next line if the toolbar's already there
' Create the toolbar; PowerPoint will error if it already exists
Set oToolbar = CommandBars.Add(Name:=MyToolbar, _
Position:=msoBarFloating, Temporary:=True)
If Err.Number <> 0 Then
' The toolbar's already there, so we have nothing to do
Exit Sub
End If
On Error GoTo ErrorHandler
' Now add a button to the new toolbar
Set oButton = oToolbar.Controls.Add(Type:=msoControlButton)
' And set some of the button's properties
With oButton
.DescriptionText = "Get cursor position"
'Tooltip text when mouse if placed over button
.Caption = "Get cursor position"
'Text if Text in Icon is chosen
.OnAction = "Button1"
'Runs the Sub Button1() code when clicked
.Style = msoButtonIcon
' Button displays as icon, not text or both
.FaceId = 38
' chooses icon #52 from the available Office icons
End With
' Now add a button to the new toolbar
Set oButton2 = oToolbar.Controls.Add(Type:=msoControlButton)
' And set some of the button's properties
With oButton2
.DescriptionText = "Paste at cursor"
'Tooltip text when mouse if placed over button
.Caption = "Paste at cursor"
'Text if Text in Icon is chosen
.OnAction = "Button2"
'Runs the Sub Button1() code when clicked
.Style = msoButtonIcon
' Button displays as icon, not text or both
.FaceId = 40
' chooses icon #52 from the available Office icons
End With
' Repeat the above for as many more buttons as you need to add
' Be sure to change the .OnAction property at least for each new button
' You can set the toolbar position and visibility here if you like
' By default, it'll be visible when created. Position will be ignored in PPT 2007 and later
oToolbar.Top = 150
oToolbar.Left = 150
oToolbar.Visible = True
NormalExit:
Exit Sub ' so it doesn't go on to run the errorhandler code
ErrorHandler:
'Just in case there is an error
MsgBox Err.Number & vbCrLf & Err.Description
Resume NormalExit:
End Sub
Sub Button1()
GetCursorPos Hold
End Sub
Sub Button2()
Dim zoom As Double
zoom = ActiveWindow.View.zoom / 100
With ActivePresentation.Slides(1).Shapes.Paste
.Left = ConvertPixelToPointX((Hold.X_Pos - GetSlideOriginOnScreen(ActiveWindow).X_Pos) / zoom)
.Top = ConvertPixelToPointY((Hold.Y_Pos - GetSlideOriginOnScreen(ActiveWindow).Y_Pos) / zoom)
End With
End Sub
IIRC, GetCursorPos returns the cursor position relative to the upper left of the Windows screen, regardless of the current application, and the units are (?) twips (?) ... not sure. PowerPoint works in points (1/72 of an inch).
This may be useful. PointsToScreenPixelsX and Y return the offset (in pixels) from the upper left of the Windows screen to the place on the PPT Slide specified. In this case, I've used 0, so you'd get the position of the upper left corner of the slide (NOT the PPT window, mind). If the entire PPT Window is slid off the left and top of screen until just the upper left of the current slide is showing, this'll return 0s. If you then zoom in, it'll return negative numbers; even if you can't SEE the entire slide, it's there and the top left corner is off in minus-land somewhere. ;-)
Sub WhereAreWe()
Dim xPixels As Long
Dim yPixels As Long
With ActiveWindow
xPixels = .PointsToScreenPixelsX _
(0)
yPixels = .PointsToScreenPixelsY _
(0)
End With
Debug.Print xPixels & vbTab & yPixels
End Sub
As Steve as said, the PointsToScreenPixelX(0) and PointsToScreenPixelY(0) properties will give the screen co-ordinates of the top-left edge of slide in slide/normal view. If you include the code below then it will position the shape at whatever cursor position you store.
Note that this snippet works at zoom level 100 for the window. For other values, you will have to scale accordingly.
Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Function GetSlideOriginOnScreen(Window As DocumentWindow) As POINTAPI
Dim pt As POINTAPI
With Window
pt.X_Pos = .PointsToScreenPixelsX(0)
pt.Y_Pos = .PointsToScreenPixelsY(0)
End With
GetSlideOriginOnScreen = pt
End Function
Function ConvertPixelToPointX(X As Long) As Single
Const LOGPIXELSX = 88
Const POINTSPERINCH = 72
Dim hDC As Long
Dim sngX As Long
hDC = GetDC(0)
sngX = GetDeviceCaps(hDC, LOGPIXELSX)
Call ReleaseDC(0, hDC)
ConvertPixelToPointX = (X / sngX) * POINTSPERINCH
End Function
Function ConvertPixelToPointY(Y As Long) As Single
Const LOGPIXELSY = 90
Const POINTSPERINCH = 72
Dim hDC As Long
Dim sngY As Long
hDC = GetDC(0)
sngY = GetDeviceCaps(hDC, LOGPIXELSY)
Call ReleaseDC(0, hDC)
ConvertPixelToPointY = (Y / sngY) * POINTSPERINCH
End Function
Now change your code to the following call:
Sub Paste()
With ActivePresentation.Slides(1).Shapes.Paste(1)
.Left = ConvertPixelToPointX(Hold.X_Pos - GetSlideOriginOnScreen(ActiveWindow).X_Pos)
.Top = ConvertPixelToPointY(Hold.Y_Pos - GetSlideOriginOnScreen(ActiveWindow).Y_Pos)
End With
End Sub
Related
I am looking for codes that would screenshot display in my screen (not the whole screen). I got already a program with the help of google but unfortunately, the program is just pasting the screenshot in excel. How could I paste it directly to new email in Outlook? Thanks. Btw here's the code that I got.
Option Explicit
Private Const CCHDEVICENAME = 32
Private Const CCHFORMNAME = 32
Private Const SRCCOPY = &HCC0020 ' (DWORD) destination = source
Private Type DEVMODE
dmDeviceName As String * CCHDEVICENAME
dmSpecVersion As Integer
dmDriverVersion As Integer
dmSize As Integer
dmDriverExtra As Integer
dmFields As Long
dmOrientation As Integer
dmPaperSize As Integer
dmPaperLength As Integer
dmPaperWidth As Integer
dmScale As Integer
dmCopies As Integer
dmDefaultSource As Integer
dmPrintQuality As Integer
dmColor As Integer
dmDuplex As Integer
dmYResolution As Integer
dmTTOption As Integer
dmCollate As Integer
dmFormName As String * CCHFORMNAME
dmUnusedPadding As Integer
dmBitsPerPel As Long
dmPelsWidth As Long
dmPelsHeight As Long
dmDisplayFlags As Long
dmDisplayFrequency As Long
End Type
Declare Function ReleaseDC Lib "user32.dll" (ByVal hWnd As Long, ByVal hdc As Long) As Long
Declare Function OpenClipboard Lib "user32.dll" (ByVal hWnd As Long) As Long
Declare Function EmptyClipboard Lib "user32.dll" () As Long
Declare Function SetClipboardData Lib "user32.dll" (ByVal wFormat As Long, ByVal hMem As Long) As Long
Declare Function CloseClipboard Lib "user32.dll" () As Long
Declare Function SelectObject Lib "gdi32.dll" (ByVal hdc As Long, ByVal hObject As Long) As Long
Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
Declare Function BitBlt Lib "gdi32.dll" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Declare Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As DEVMODE) As Long
Declare Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Declare Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
Declare Function CountClipboardFormats Lib "user32" () As Long
Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Declare Function CreateIC Lib "GDI32" Alias "CreateICA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As Long) As Long
Declare Function GetDeviceCaps Lib "GDI32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Declare Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
Sub GetPrintScreen()
Call CaptureScreen(35, 200, 975, 445)
End Sub
I think this is the part that I should edit.
Public Sub ScreenToGIF_NewWorkbook()
Dim wbDest As Workbook, wsDest As Worksheet
Dim FromType As String, PicHigh As Single
Dim PicWide As Single, PicWideInch As Single
Dim PicHighInch As Single, DPI As Long
Dim PixelsWide As Integer, PixelsHigh As Integer
Call TOGGLEEVENTS(False)
Call GetPrintScreen
If CountClipboardFormats = 0 Then
MsgBox "Clipboard is currently empty.", vbExclamation, "Nothing to Paste"
GoTo EndOfSub
End If
'Determine the format of the current clipboard contents. There may be multiple
'formats available but the Paste methods below will always (?) give priority
'to enhanced metafile (picture) if available so look for that first.
If IsClipboardFormatAvailable(14) <> 0 Then
FromType = "pic"
ElseIf IsClipboardFormatAvailable(2) <> 0 Then
FromType = "bmp"
Else
MsgBox "Clipboard does not contain a picture or bitmap to paste.", _
vbExclamation, "No Picture"
Exit Sub
End If
Application.StatusBar = "Pasting from clipboard ..."
Set wbDest = Workbooks.Add(xlWBATWorksheet)
Set wsDest = wbDest.Sheets(1)
wbDest.Activate
wsDest.Activate
wsDest.Range("B3").Activate
'Paste a picture/bitmap from the clipboard (if possible) and select it.
'The clipboard may contain both text and picture/bitmap format items. If so,
'using just ActiveSheet.Paste will paste the text. Using Pictures.Paste will
'paste a picture if a picture/bitmap format is available, and the Typename
'will return "Picture" (or perhaps "OLEObject"). If *only* text is available,
'Pictures.Paste will create a new TextBox (not a picture) on the sheet and
'the Typename will return "TextBox". (This condition now checked above.)
On Error Resume Next 'just in case
wsDest.Pictures.Paste.Select
On Error GoTo 0
'If the pasted item is an "OLEObject" then must convert to a bitmap
'to get the correct size, including the added border and matting.
'Do this via a CopyPicture-Bitmap and then a second Pictures.Paste.
If TypeName(Selection) = "OLEObject" Then
With Selection
.CopyPicture Appearance:=xlScreen, Format:=xlBitmap
.Delete
ActiveSheet.Pictures.Paste.Select
'Modify the FromType (used below in the suggested file name)
'to signal that the original clipboard image is not being used.
FromType = "ole object"
End With
End If
'Make sure that what was pasted and selected is as expected.
'Note this is the Excel TypeName, not the clipboard format.
If TypeName(Selection) = "Picture" Then
With Selection
PicWide = .Width
PicHigh = .Height
.Delete
End With
Else
'Can get to here if a chart is selected and "Copy"ed instead of "Copy Picture"ed.
'Otherwise, ???.
If TypeName(Selection) = "ChartObject" Then
MsgBox "Use Shift > Edit > Copy Picture on charts, not just Copy.", _
vbExclamation, "Got a Chart Copy, not a Chart Picture"
Else
MsgBox "Excel pasted a '" & TypeName(Selection) & "' instead of a Picture.", _
vbExclamation, "Not a Picture"
End If
'Clean up and quit.
ActiveWorkbook.Close SaveChanges:=False
GoTo EndOfSub
End If
'Add an empty embedded chart, sized as above, and activate it.
'Positioned at cell B3 just for convenient debugging and final viewing.
'Tip from Jon Peltier: Just add the embedded chart directly, don't use the
'macro recorder method of adding a new separate chart sheet and then relocating
'the chart back to a worksheet.
With Sheets(1)
.ChartObjects.Add(.Range("B3").Left, .Range("B3").Top, PicWide, PicHigh).Activate
End With
'Paste the [resized] bitmap into the ChartArea, which creates ActiveChart.Shapes(1).
On Error Resume Next
ActiveChart.Pictures.Paste.Select
On Error GoTo 0
If TypeName(Selection) = "Picture" Then
With ActiveChart
'Adjust the position of the pasted picture, aka ActiveChart.Shapes(1).
'Adjustment is slightly greater than the .ChartArea.Left/Top offset, why ???
'''' .Shapes(1).IncrementLeft -1
'''' .Shapes(1).IncrementTop -4
'Remove chart border. This must be done *after* all positioning and sizing.
' .ChartArea.Border.LineStyle = 0
End With
'Show pixel size info above the picture-in-chart-soon-to-be-GIF/JPEG/PNG.
PicWideInch = PicWide / 72 'points to inches ("logical", not necessarily physical)
PicHighInch = PicHigh / 72
DPI = PixelsPerInch() 'typically 96 or 120 dpi for displays
PixelsWide = PicWideInch * DPI
PixelsHigh = PicHighInch * DPI
Else
'Something other than a Picture was pasted into the chart.
'This is very unlikely.
MsgBox "Clipboard corrupted, possibly by another task."
End If
EndOfSub:
Call TOGGLEEVENTS(True)
End Sub
Public Sub TOGGLEEVENTS(blnState As Boolean)
'Originally written by Zack Barresse
With Application
.DisplayAlerts = blnState
.EnableEvents = blnState
.ScreenUpdating = blnState
If blnState Then .CutCopyMode = False
If blnState Then .StatusBar = False
End With
End Sub
Public Function PixelsPerInch() As Long
Application.DefaultWebOptions.PixelsPerInch.
Dim hdc As Long
hdc = CreateIC("DISPLAY", vbNullString, vbNullString, 0)
PixelsPerInch = GetDeviceCaps(hdc, 88) 'LOGPIXELSX = 88 = Logical pixels/inch in X
DeleteDC (hdc)
End Function
Public Sub CaptureScreen(Left As Long, Top As Long, Width As Long, Height As Long)
Dim srcDC As Long, trgDC As Long, BMPHandle As Long, dm As DEVMODE
srcDC = CreateDC("DISPLAY", "", "", dm)
trgDC = CreateCompatibleDC(srcDC)
BMPHandle = CreateCompatibleBitmap(srcDC, Width, Height)
SelectObject trgDC, BMPHandle
BitBlt trgDC, 0, 0, Width, Height, srcDC, Left, Top, SRCCOPY
OpenClipboard 0&
EmptyClipboard
SetClipboardData 2, BMPHandle
CloseClipboard
DeleteDC trgDC
ReleaseDC BMPHandle, srcDC
End Sub
There is a near exact question here
Read pixel colors of an image
The Op actually asks the same question as I'm asking for. But accepts an answer that is nearly there but not quite.. The code below (taken from that thread) does everything I need bar the pixel by pixel bit. If you click on an image it will give you the colour at the click site. As I want to scan the whole picture I though I'd just do an X Y scan and put the sequential X and Y's in instead of the returned X and Y of the GetCursorPos call. But how to get left position and width (for example) in pixels to start the scan? What would I put in my for next loop to address each pixel ?
So to clarify my question.
How to change the code below to scan every pixel of the image not just the clicked cursor position. Thanks
#If VBA7 Then
Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hdc As LongPtr, ByVal x As Long, ByVal y As Long) As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (ByRef lpPoint As POINT) As LongPtr
Private Declare PtrSafe Function GetWindowDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
#Else
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As POINT) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
#End If
Private Type POINT
x As Long
y As Long
End Type
Sub Picture1_Click()
Dim pLocation As POINT
Dim lColour As Long
Dim lDC As Variant
lDC = GetWindowDC(0)
Call GetCursorPos(pLocation)
lColour = GetPixel(lDC, pLocation.x, pLocation.y)
Range("a1").Interior.Color = lColour
End Sub
Option Explicit
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type POINTAPI
X As Long
Y As Long
End Type
Dim IDC As Long
Private Function ScreenDPI(bVert As Boolean) As Long
'*** Get screen DPI ***
Static lDPI&(1), lDC&
If lDPI(0) = 0 Then
lDC = GetDC(0)
lDPI(0) = GetDeviceCaps(lDC, 88&) 'horz
lDPI(1) = GetDeviceCaps(lDC, 90&) 'vert
lDC = ReleaseDC(0, lDC)
End If
ScreenDPI = lDPI(Abs(bVert))
End Function
Private Function PTtoPX(Points As Single, bVert As Boolean) As Long
'*** Swap Points to pixels ****
PTtoPX = Points * ScreenDPI(bVert) / 72
End Function
Sub GetImageRect(ByRef RC As RECT)
Dim RNG As Range
Set RNG = Sheet1.Range("A1")
'**** using the spread sheet cell A1 as a reference ***
'** find the details of th eimage and convert to pixels ***
Dim wnd As Window
Set wnd = RNG.Parent.Parent.Windows(1)
With Sheet1.Image1
RC.Left = PTtoPX(.Left * wnd.Zoom / 100, 0) + wnd.PointsToScreenPixelsX(0)
RC.Top = PTtoPX(.Top * wnd.Zoom / 100, 1) + wnd.PointsToScreenPixelsY(0)
RC.Right = PTtoPX(.Width * wnd.Zoom / 100, 0) + RC.Left
RC.Bottom = PTtoPX(.Height * wnd.Zoom / 100, 1) + RC.Top
End With
End Sub
Sub XYScanOfImage()
'*** put an active X image on sheet1 call it image1 and run this routine **
'** to get the colour information for each pixel *****
Dim RC As RECT
Dim ScanX As Single
Dim ScanY As Single
Dim ImX As Single
Dim ImY As Single
Dim PixCol As Single
Call GetImageRect(RC)
ImX = RC.Left
ImY = RC.Top
IDC = GetDC(0)
'*** scan image left to right top to bottom ****
For ScanX = RC.Left To RC.Right
For ScanY = RC.Top To RC.Bottom
PixCol = GetPixel(IDC, ScanX, ScanY)
'**** PUT CODE IN HERE TO PROCESS THE PIXEL COLOUR ****
Next
Next
IDC = ReleaseDC(0, IDC)
End Sub
The answer from perfo is fantastic - and works!
Some notes to help:
You have to add the image as an ActiveX control (go to the Excel Developer ribbon to to do this, then right-click on the image and set its name to Image1 - this should be the default anyway).
Also, make sure it's on a sheet called Sheet1 in VBA (again, this should be the default).
The routine returns an RGBA colour. I created the following routine to colour each cell:
Sub ColourCell(c As Range, ThisColour As Single)
'colour the passed in range
Dim Red As Byte
Dim Green As Byte
Dim Blue As Byte
Red = ThisColour And &HFF&
Green = (ThisColour And &HFF00&) / 256
Blue = (ThisColour And &HFF0000) / 65535
c.Interior.Color = RGB(Red, Green, Blue)
End Sub
I amended the answer to colour the cells from the active cell down and across as follows:
'*** scan image left to right top to bottom ****
Dim i As Integer
Dim j As Integer
Dim OriginalRowNumber As Integer
Dim OriginalColumnNumber As Integer
OriginalRowNumber = ActiveCell.Row
OriginalColumnNumber = ActiveCell.Column
i = OriginalRowNumber
j = OriginalColumnNumber
Sheet1.Select
Cells.EntireColumn.ColumnWidth = 0.63
Cells.EntireRow.RowHeight = 6
For ScanX = RC.Left To RC.Right
For ScanY = RC.Top To RC.Bottom
PixCol = GetPixel(IDC, ScanX, ScanY)
ColourCell Cells(j, i), PixCol
j = j + 1
Next
i = i + 1
If i Mod 5 = 0 Then Stop
j = OriginalColumnNumber
Next
There are a couple of extra bits in there to set a tiny grid size, so you can see your picture unfolding, and a debug line to pause the macro every 5 columns (it takes a LONG time to run).
I want to be able to create a graph, with the top left of it being on my cursor position. Is that possible? Can the (X,Y) of my mouse be converted into a range format?
Hm, it's not exactly built in AFAIK, but I found this page which gives a suggestion that worked for me:
In a module, put this at the top:
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function ScreenToClient Lib "user32" (ByVal hWnd As Long, _
lpPoint As POINTAPI) As Long
Private Type POINTAPI
X As Long
Y As Long
End Type
Then, for the subroutines to get the mouseX and mouseY, put this somewhere below:
Function MouseX(Optional ByVal hWnd As Long) As Long
' Get mouse X coordinates in pixels
'
' If a window handle is passed, the result is relative to the client area
' of that window, otherwise the result is relative to the screen
Dim lpPoint As POINTAPI
Application.Volatile(false)
GetCursorPos lpPoint
If hWnd Then ScreenToClient hWnd, lpPoint
MouseX = lpPoint.X
End Function
and
Function MouseY(Optional ByVal hWnd As Long) As Long
' Get mouse Y coordinates in pixels
'
' If a window handle is passed, the result is relative to the client area
' of that window, otherwise the result is relative to the screen
Dim lpPoint As POINTAPI
Application.Volatile(false)
GetCursorPos lpPoint
If hWnd Then ScreenToClient hWnd, lpPoint
MouseY = lpPoint.Y
End Function
Then, in Excel, if you simply enter into a cell =mouseX() it'll return the mouseX position when you hit ENTER. Same with =mouseY().
Trying it out, I did:
Sub chart_Test()
ActiveSheet.Shapes.AddChart.Select
ActiveChart.ChartType = xlLine
ActiveSheet.Shapes("Chart 1").Top = MouseY()
ActiveSheet.Shapes("Chart 1").Left = MouseX()
End Sub
and got it to work.
edit: Note, I'm not as good with charts as other things in VBA, so as you create charts, you'll need to edit the .Shapes("Chart 1"). part to whatever chart name/number you're on. Or iterate through them.
Not sure about the mouse x y but you could get the range on worksheet selection change. Put the chart at that location.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Chart.Left = Target.column
Chant.Top = Target.row
End Sub
I can manually shorten or lengthen the Name Box (which is just to the left of the Formula Bar) by dragging the "dot" to the right or left. (This also shortens or lengthens the Formula Bar.)
How can I do the adjustment with VBA??
PHEW!!!!
Things that you throw my way!!! :P
When I realized that there are is no native way to achieve what you want, I resorted to the API way but then I was again disappointed because the "Name Box" only exposed WS_CHILDWINDOW, WS_VISIBLE, CBS_DROPDOWN, CBSAUTOHSCROLL and CBS_HASSTRINGS. The "Dot" doesn't even have a handle.
Out of frustration, I started thinking along the lines of what Mark proposed in his answer. The Registry way. It took me some 20 odd mins to find the Registry key. But Alas, that joy also didn't last long when I realized that changing the registry key didn't have any effect till I restarted Excel.
After this there was only one way left Simulation of the mouse. I would have smashed my laptop on the ground if that didn't work!.
I tried with some hardcoded values in the beginning and was happy with the results. So here is the final version...
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
Private Declare Function SetCursorPos Lib "user32" _
(ByVal X As Integer, ByVal Y As Integer) As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Sub mouse_event Lib "user32.dll" (ByVal dwFlags As Long, _
ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Private Const MOUSEEVENTF_MOVE = &H1 ' mouse move
Private Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
Private Const MOUSEEVENTF_LEFTUP = &H4 ' left button up
Private Const MOUSEEVENTF_ABSOLUTE = &H8000 ' absolute move
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Dim pos As RECT
Sub Sample()
Dim hwndExcel As Long
Dim hwndPanel As Long
Dim hwndCombo As Long
Dim dest_x As Long
Dim dest_y As Long
Dim cur_x As Long
Dim cur_y As Long
Dim Position As POINTAPI
'~~> Get the handle of the Excel Window
hwndExcel = FindWindow("XLMAIN", Application.Caption)
If hwndExcel = 0 Then Exit Sub
'MsgBox "Excel Window Found"
'~~> Get the handle of the Panel where the Name Box is
hwndPanel = FindWindowEx(hwndExcel, ByVal 0&, "EXCEL;", vbNullString)
If hwndPanel = 0 Then Exit Sub
'MsgBox "Excel Panel Found"
hwndCombo = FindWindowEx(hwndPanel, ByVal 0&, "Combobox", vbNullString)
If hwndCombo = 0 Then Exit Sub
'MsgBox "Excel Name Box Found"
'~~> Retrieve the dimensions of the bounding rectangle of the
'~~> specified window. The dimensions are given in screen
'~~> coordinates that are relative to the upper-left corner of the screen.
GetWindowRect hwndCombo, pos
'~~> Get the approx location of the DOT. It is where the Combobox ends
cur_x = pos.Right
cur_y = pos.Top + 10
'~~> New Destination
dest_x = cur_x + 500 '<~~ Change width here
dest_y = cur_y
'~~> Move the cursor to the specified screen coordinates of the DOT.
SetCursorPos cur_x, cur_y
Wait 1 '<~~ Wait 1 second
'~~> Press the left mouse button on the DOT
mouse_event MOUSEEVENTF_LEFTDOWN, cur_x, cur_y, 0, 0
'~> Set the new destination. Take cursor there
SetCursorPos dest_x, dest_y
'~~> Press the left mouse button again to release it
mouse_event MOUSEEVENTF_LEFTUP, dest_x, dest_y, 0, 0
Wait 1
MsgBox "done"
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
Instructions
Paste this code in a module and then from the sheet press ALT+F8 and then select Sample and press ALT+R
Tested in Excel 2010
Before
After
As there isn't a NameBox object within VBA Excel.Application I don't think it's possible in native VBA.
You'd have to delve into REGISTRY. The registry key is
Note: Even if you set the value, for it to take effect, you will have to close and open Excel.
I have a UserForm of a MonthView and DTPicker that will populate when certain cells are clicked.
I have the form positioned directly below the first cell.
I would like it positioned right below each active cell that I tell it to activate on.
My activate code to position the userform:
Private Sub UserForm_Activate()
With frmCalendar
.Top = Application.Top + 340
.Left = Application.Left + 330
End With
End Sub
My worksheet selection change code, which will launch the userform upon certain cell clicks:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Application.Intersect(Target, Range("H10,H15")) Is Nothing Then
frmCalendar.Show
End If
End Sub
I know there are add-ins that help do this, but I'd like to figure out how to position the user form right below the cells mentioned above (H10, H14, H15) without using an add-in.
I changed the Activate Sub code
Private Sub UserForm_Activate()
With frmCalendar
.Top = ActiveCell.offset(31).Top
.Left = ActiveCell.offset(1).Left
End With
End Sub
This moves it slightly below and slightly to the right of the cell, but when I try it on another cell is moves further down but stays the same distance to the right. This still is messy.
Is there no way to position this form directly below the ActiveCell using these methods?
You are using the correct Event macro. I placed a TextBox in the worksheet and with this macro
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim s As Shape
Set s = ActiveSheet.Shapes(1)
s.Top = ActiveCell.Offset(1, 1).Top
s.Left = ActiveCell.Offset(1, 1).Left
End Sub
I can get the TextBox to move just to the right and just below the activecell.
I found http://www.vbaexpress.com/forum/archive/index.php/t-22038.html and developed this which I've used:
Sub showUform(iRow&, iCol&)
Dim x11!, y11!
ActiveSheet.Cells(iRow, iCol).Select
x11 = ActiveWindow.PointsToScreenPixelsX(ActiveSheet.Cells(1, 1))
y11 = ActiveWindow.PointsToScreenPixelsY(ActiveSheet.Cells(1, 1))
UserForm1.Left = x11 + ActiveSheet.Cells(iRow, iCol).Left
UserForm1.Top = y11 + ActiveSheet.Cells(iRow, iCol).Top
UserForm1.Show
End Sub
Sub FormToActCell(UF As Object, Optional RaD$ = "ACAD", Optional Topw% = 102, _
Optional TopH% = -120)
' form to Active cell or RaD as range address ,offsets topW topH
Dim Px&, Py&, Zoomp!
If RaD = "ACAD" Then RaD = ActiveCell.Address
Set CellToRange = Range(RaD)
With CellToRange ' get point about object to
Px = (.Left + .Width * Topw / 100)
Py = (.Top + .Height * TopH / 100)
End With
Zoomp = ActiveWindow.Zoom / 100
With UF ' assuming screen as normal pts to pix of 3:4
.Left = Px * Zoomp + ActiveWindow.PointsToScreenPixelsX(0) * 0.75
.Top = Py * Zoomp + ActiveWindow.PointsToScreenPixelsY(0) * 0.75
End With
End Sub
Please see the answer I provided to this question as I believe this question is the same.
How do I properly align UserForm next to active cell
by declaring the GetDeviceCaps , GetDC , ReleaseDC functions , I repositioned the userform next to each the clicked activecell .
(I checked the codes in 32-bit and 64-bit versions of Excel)
Type POINTAPI
X As Long
Y As Long
End Type
#If VBA7 Then
Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
Dim hDc As LongPtr
#Else
Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDc As Long, ByVal nIndex As Long) As Long
Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDc As Long) As Long
Dim hDc As Long
#End If
...
Source of codes & sample file