Use .png as custom ribbon icon in Access 2007 - ms-access

I'd like to use a .png as a custom icon in the Access 2007 ribbon.
Here's what I've tried so far:
I am able to load .bmp's and .jpg's as custom images without any problem. I can load .gif's, but it doesn't seem to preserve the transparency. I can't load .png's at all. I'd really like to use .png's to take advantage of the alpha-blending that is not available in the other formats.
I found a similar question on SO, but that just deals with loading custom icons of any kind. I am specifically interested in .png's. There is an answer from Albert Kallal to that question that links to a class module he had written that appears to do exactly what I want:
meRib("Button1").Picture = "HappyFace.png"
Unfortunately, the link in that answer is dead.
I also found this site which offers a download of a 460 line module full of dozens of API calls to get support for transparent icons. Before I go that route I wanted to ask the experts here if they know of a better way.
I know .png is pretty new-fangled and all, but I'm hoping the Office development folks slipped in some native support for the format.

Here is what I am currently using. Albert Kallal has a more full-fledged solution for Access 2007 ribbon programming that does a lot more than just load .png's. I am not using it yet, but it's worth checking out.
For those who are interested, here is the code that I am using. I believe this is pretty close to the minimum required for .png support. If there's anything extraneous here, let me know and I'll update my answer.
Add the following to a standard code module:
Option Compare Database
Option Explicit
'================================================================================
' Declarations required to load .png's in Ribbon
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Type PICTDESC
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
Private Declare Function GdiplusStartup Lib "GDIPlus" (token As Long, _
inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As Long
Private Declare Function GdipCreateBitmapFromFile Lib "GDIPlus" (ByVal filename As Long, bitmap As Long) As Long
Private Declare Function GdipCreateHBITMAPFromBitmap Lib "GDIPlus" (ByVal bitmap As Long, _
hbmReturn As Long, ByVal background As Long) As Long
Private Declare Function GdipDisposeImage Lib "GDIPlus" (ByVal image As Long) As Long
Private Declare Function GdiplusShutdown Lib "GDIPlus" (ByVal token As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As PICTDESC, _
RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
'================================================================================
Public Sub GetRibbonImage(ctl As IRibbonControl, ByRef image)
Dim Path As String
Path = Application.CurrentProject.Path & "\Icons\" & ctl.Tag
Set image = LoadImage(Path)
End Sub
Private Function LoadImage(ByVal strFName As String) As IPicture
Dim uGdiInput As GdiplusStartupInput
Dim hGdiPlus As Long
Dim hGdiImage As Long
Dim hBitmap As Long
uGdiInput.GdiplusVersion = 1
If GdiplusStartup(hGdiPlus, uGdiInput) = 0 Then
If GdipCreateBitmapFromFile(StrPtr(strFName), hGdiImage) = 0 Then
GdipCreateHBITMAPFromBitmap hGdiImage, hBitmap, 0
Set LoadImage = ConvertToIPicture(hBitmap)
GdipDisposeImage hGdiImage
End If
GdiplusShutdown hGdiPlus
End If
End Function
Private Function ConvertToIPicture(ByVal hPic As Long) As IPicture
Dim uPicInfo As PICTDESC
Dim IID_IDispatch As GUID
Dim IPic As IPicture
Const PICTYPE_BITMAP = 1
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
With uPicInfo
.Size = Len(uPicInfo)
.Type = PICTYPE_BITMAP
.hPic = hPic
.hPal = 0
End With
OleCreatePictureIndirect uPicInfo, IID_IDispatch, True, IPic
Set ConvertToIPicture = IPic
End Function
Then, if you don't already have one, add a table named USysRibbons. (NOTE: Access treats this table as a system table, so you'll have to show those in your nav pane by going to Access Options --> Current Database --> Navigation Options and make sure 'Show System Objects' is checked.) Then add these attributes to your control tag:
getImage="GetRibbonImage" tag="Acq.png"
For example:
<button id="MyButtonID" label="Do Something" enabled="true" size="large"
getImage="GetRibbonImage" tag="MyIcon.png" onAction="MyPublicSub"/>

Related

Issue writing visual basic program dealing with mathematical functions and IF logic

First time posting on this website; mainly because I'm running into a huge issue with a question on my visual basic class that we were assigned. The full question can be seen here.
Basically, I'm running into issues where I THINK I am correctly executing the Buckling Load function as instructed, but I don't know how to get each different True or False value into the lstOut box.
Public Class Form1
Private Sub btnCompute_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCompute.Click
Dim area As Double
Dim length As Double
Dim width As Double
Dim load As Double
Dim buckling1 As String
Dim buckling2 As String
Dim buckling3 As String
length = CDbl(txtIn.Text)
load = CDbl(txtLbs.Text)
buckling1 = Test1(load, length, 2, area)
buckling2 = Test1(load, length, 4, area)
buckling3 = Test1(load, length, 6, area)
End Sub
Private Function Test1(ByVal load As Double, ByVal length As Double, ByVal width As Double, ByVal area As Double) As Boolean
If ((0.3 * 1700000 * (length * width)) / (length / width) ^ 2) > load Then
Return True
Else
Return False
End If
End Function
End Class
And that's only the first part...I'm really struggling with this question. I'm somewhat new to programming, and the concept of functions are pretty frightening. Does anyone have any tips or advice they could give me?
Note that I'm not asking for the whole question to be completed, I'm just trying to figure out how I'm going to put all of output from the Test1, Test2, and Test3 functions into the lstOut box. I hope I'm making at least some sense.
You can set the buckling variables to Boolean as Suggested by Mark Hall, or pass the words "True" or "False" instead as string so that your Buckling variables accept them
IE simply add Double Quote(s) to True (turning it to "True") and False (turning it to "False")

Where do I put Access vba code to only fire when physically printing?

I have some code that I want to fire when the user physically prints the report. Not when Print Previewing, etc, but only when sending to the printer. The user needs to be able to pull up the report and view it, then if they decide to print, the vba code will take over and write some info to a different table than is being used to generate the report. I was hoping not to have to place a Print button on the actual report (even though I know I can hide it for the print), so I was wondering if I could somehow trap the Print dialog instead.
Has anyone ever had any luck doing so?
After much consideration, I think the best way to accomplish this is by identifying the Active Window text during the report Page event. During a print preview, this text will be the name of the database itself, something like "Microsoft Access - DatabaseName : Database (Access 2003). During a real printing operation, the active window will be "Printing"
I credit most of the code as coming from this source.
Declare Function GetActiveWindow Lib "user32" () As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Sub Report_Page()
On Error GoTo PrintError
Dim strCaption As String
Dim lngLen As Long
' Create string filled with null characters.
strCaption = String$(255, vbNullChar)
' Return length of string.
lngLen = Len(strCaption)
' Call GetActiveWindow to return handle to active window,
' and pass handle to GetWindowText, along with string and its length.
If (GetWindowText(GetActiveWindow, strCaption, lngLen) > 0) Then
' Return value that Windows has written to string.
ActiveWindowCaption = strCaption
End If
If ActiveWindowCaption = "Printing" Then
'
' Special activity goes here.
'
End If
Exit Sub
PrintError:
' Just in case
End Sub

Download file with progress meter in VBA

I've found numerous examples for doing this in several languages, but none that are VBA specific. This question, How to download multiple files in VB6 with progress bar?, addresses three different approaches to do this in VB6.
Use the ASyncRead property of the VB6 UserControl/UserDocument objects
Use type library olelib.tlb and the IBindStatusCallback interface
Use wininet.dll to write your own download to file function
None of these approaches work for me because:
The UserControl/UserDocument objects are not available from VBA
I'd rather not have to maintain and distribute a large external dependency
I did not see an obvious way to get the current file download progress
Number 2 above seemed the most promising. I'm wondering if I can create an IBindStatusCallback interface using a class module from within my VBA project?
Or maybe there are properties/methods available using Number 3 above that would provide the current progress. Any help is much appreciated.
I have done this using the wininet.dll functions. Unfortunately I cannot paste my code as it is owned by my employer.
Use InternetOpen and InternetOpenUrl to start the download, HttpQueryInfoLong to get the content length and then repeatedly call InternetReadFile to read data into a buffer (I use a 128k buffer), writing the data to a file and updating the progress bar as you go.
Declarations to get you started:
Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" (ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, ByVal sProxyBypass As String, ByVal lFlags As Long) As Long
Private Declare Function HttpQueryInfo Lib "wininet.dll" Alias "HttpQueryInfoA" (ByVal hHttpRequest As Long, ByVal lInfoLevel As Long, ByRef sBuffer As Any, ByRef lBufferLength As Long, ByRef lIndex As Long) As Long
Private Declare Function InternetCloseHandle Lib "wininet.dll" (ByVal hInet As Long) As Integer
Private Declare Function InternetReadFile Lib "wininet.dll" (ByVal hFile As Long, ByRef Buffer As Any, ByVal lNumberOfBytesToRead As Long, lNumberOfBytesRead As Long) As Integer
Private Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" (ByVal hInternet As Long, ByVal lpszUrl As String, ByVal lpszHeaders As String, ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
Private Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Private Const INTERNET_FLAG_RELOAD = &H80000000
Private Const INTERNET_FLAG_KEEP_CONNECTION = &H400000 ' use keep-alive semantics - required for NTLM proxy authentication
Private Const HTTP_QUERY_CONTENT_LENGTH = 5
Private Const HTTP_QUERY_FLAG_NUMBER = &H20000000
If you need any clarification, post a comment.
You want a progress bar in VBA, wouldn't one of these approaches work?
Progress bar in VBA Excel
Seems a lot simpler than doing it as you describe, or am I not understanding?
OK, try this. Get the headers from the URL and parse them for Content-Length. Then you can set your progress bar accordingly.
Function GetFileSize(URL As String) As Long
Dim xml As Object ' MSXML2.XMLHTTP60
Dim result As String
Set xml = CreateObject("MSXML2.XMLHTTP.6.0")
With xml
' get headers only
.Open "HEAD", URL, False
.send
End With
result = xml.getResponseHeader("Content-Length")
GetFileSize = CLng(result)
End Function
Now just call the function with the URL of the file you want to download. It should give you the number of bytes of the file.

Forms GotFocus event does not seem to fire

I'm trying to call an event when the user returns focus to the Access application when a specific form is open. The following event doesn't seem to fire up at all.
Private Sub Form_GotFocus()
Call crtListDirectory
End Sub
Does any body have any ideas as to how I could trigger this event to happen, and when/how does the Form_GotFocus event actually get triggered.
thanks in advance for any help
Noel
Access help:
A form can get the focus only if all
visible controls on a form are
disabled, or there are no controls on
the form.
You might like to try Activate.
EDIT re Comments
The only way I can see of doing what you seem to want is with APIs, which is somewhat messy. To demonstrate this you will need a form with two controls Text0 and Text2 (these are the default names). Set the Timer Interval to something suitable, say 2000, and the Timer Event to:
Private Sub Form_Timer()
Dim lngWin As Long
Dim s As String
'This is just a counter to show that the code is running
Me.Text2 = Nz(Me.Text2, 0) + 1
'API
lngWin = GetActiveWindow()
s = GetWinName(lngWin)
If s = "Microsoft Access" Then
If Me.Text0 = "Lost Focus" Then
Me.Text0 = "Focus returned"
End If
Else
Me.Text0 = "Lost Focus"
End If
End Sub
You will now need a module for:
Option Compare Database
Declare Function GetActiveWindow Lib "user32" () As Integer
Declare Function GetWindowText Lib "user32.dll" Alias _
"GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As _
String, ByVal cch As Long) As Long
Declare Function GetWindowTextLength Lib "user32" Alias _
"GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Function GetWinName(hw As Long)
Dim lngText As Long ' receives length of text of title bar
Dim strWinName As String ' receives the text of the title bar
Dim lngWinText As Long ' receives the length of the returned string
lngText = GetWindowTextLength(hw)
strWinName = Space(lngText + 1)
lngWinText = GetWindowText(hw, strWinName, lngText + 1)
strWinName = Left(strWinName, lngWinText)
GetWinName = strWinName
End Function
This is all very, very rough, but it gives you something to mess about with.

Replace Module Text in MS Access using VBA

How do I do a search and replace of text within a module in Access from another module in access? I could not find this on Google.
FYI, I figured out how to delete a module programatically:
Call DoCmd.DeleteObject(acModule, modBase64)
I assume you mean how to do this programatically (otherwise it's just ctrl-h). Unless this is being done in the context of a VBE Add-In, it is rarely (if ever) a good idea. Self modifying code is often flagged by AV software an although access will let you do it, it's not really robust enough to handle it, and can lead to corruption problems etc. In addition, if you go with self modifying code you are preventing yourself from ever being able to use an MDE or even a project password. In other words, you will never be able to protect your code. It might be better if you let us know what problem you are trying to solve with self modifying code and see if a more reliable solution could be found.
After a lot of searching I found this code:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Function to Search for a String in a Code Module. It will return True if it is found and
'False if it is not. It has an optional parameter (NewString) that will allow you to
'replace the found text with the NewString. If NewString is not included in the call
'to the function, the function will only find the string not replace it.
'
'Created by Joe Kendall 02/07/2003
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function SearchOrReplace(ByVal ModuleName As String, ByVal StringToFind As String, _
Optional ByVal NewString, Optional ByVal FindWholeWord = False, _
Optional ByVal MatchCase = False, Optional ByVal PatternSearch = False) As Boolean
Dim mdl As Module
Dim lSLine As Long
Dim lELine As Long
Dim lSCol As Long
Dim lECol As Long
Dim sLine As String
Dim lLineLen As Long
Dim lBefore As Long
Dim lAfter As Long
Dim sLeft As String
Dim sRight As String
Dim sNewLine As String
Set mdl = Modules(ModuleName)
If mdl.Find(StringToFind, lSLine, lSCol, lELine, lECol, FindWholeWord, _
MatchCase, PatternSearch) = True Then
If IsMissing(NewString) = False Then
' Store text of line containing string.
sLine = mdl.Lines(lSLine, Abs(lELine - lSLine) + 1)
' Determine length of line.
lLineLen = Len(sLine)
' Determine number of characters preceding search text.
lBefore = lSCol - 1
' Determine number of characters following search text.
lAfter = lLineLen - CInt(lECol - 1)
' Store characters to left of search text.
sLeft = Left$(sLine, lBefore)
' Store characters to right of search text.
sRight = Right$(sLine, lAfter)
' Construct string with replacement text.
sNewLine = sLeft & NewString & sRight
' Replace original line.
mdl.ReplaceLine lSLine, sNewLine
End If
SearchOrReplace = True
Else
SearchOrReplace = False
End If
Set mdl = Nothing
End Function
Check out the VBA object browser for the Access library. Under the Module object you can search the Module text as well as make replacements. Here is an simple example:
In Module1
Sub MyFirstSub()
MsgBox "This is a test"
End Sub
In Module2
Sub ChangeTextSub()
Dim i As Integer
With Application.Modules("Module1")
For i = 1 To .CountOfLines
If InStr(.Lines(i, 1), "This is a Test") > 0 Then
.ReplaceLine i, "Msgbox ""It worked!"""
End If
Next i
End With
End Sub
After running ChangeTextSub, MyFirstSub should read
Sub MyFirstSub()
MsgBox "It worked!"
End Sub
It's a pretty simple search but hopefully that can get you going.
additional for the function (looping through all the lines)
Public Function ReplaceWithLine(modulename As String, StringToFind As String, NewString As String)
Dim mdl As Module
Set mdl = Modules(modulename)
For x = 0 To mdl.CountOfLines
Call SearchOrReplace(modulename, StringToFind, NewString)
Next x
Set mdl = Nothing
End Function
Enjoy ^^