How to Edit a Read-Only Word Document (VBA) - vba

I am periodically getting Word documents from various clients and sometimes they send them to me in 'Read-Only' mode. While it isn't a big deal to go to 'View > Edit Document' manually, I cannot seem to find how to do this within my VBA code.
Either opening a document as editable or toggling it as editable once it is open would be sufficient for my needs.
Note that I cannot open the document with 'readOnly = false' as it looks like it is set to 'readOnly recommended' (based on my reading of the MS man page on Document.Open).
IN CONTEXT:
I was also hitting a problem with turning off 'read-mode' which the documents were opening as by default. I have posted this question and answer here.

The code below will change the ReadOnly attribute of a closed file, setting its ReadOnly attribute to either True or False depending on the argument supplied to the procedure.
Private Sub SetReadOnlyProperty(Fn As String, _
ByVal ReadOnly As Boolean)
' 21 Nov 2017
Dim Fso As Object
Dim Doc As Object
Set Fso = CreateObject("Scripting.FileSystemObject")
Set Doc = Fso.GetFile(Fn)
If (Doc.Attributes And vbReadOnly) <> Abs(Int(ReadOnly)) Then
Doc.Attributes = Doc.Attributes Xor vbReadOnly
End If
End Sub
This procedure requires access to the MS Scripting Runtime DLL. Enable this access by checking the box against Miscrosoft Scripting Runtime from Tools >References in the VBE window. Below is an example of how to call the function. Note that an error will result if the supplied file doesn't exist.
Private Sub TestReadOnly()
SetReadOnlyProperty "H:\Test Folder\Test File.docx", False
End Sub

Related

msoFileDialogFilePicker not displaying

I'm having issues with trying to display the file picker using the FileDialog property in MS Access 2016. I've tried both early and late binding but the file picker never displays. No errors are detected nor messages displayed. When I try to debug it line by line, the .show doesn't trigger the form. I've also tried If .show=-1 in lieu of .show but that does not work either. I've tried removing the library reference to Microsoft Office 16.0 Object Library, and even using late binding the window still does not display. Any ideas as to what is going wrong, and how to remedy it? I'm adding both early and late binding examples below.
Public Sub FP_EarlyBinding()
Dim fd As Office.FileDialog
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.AllowMultiSelect = True
.Show
For Each vrtSelectedItem In .SelectedItems
Debug.Print vrtSelectedItem
Next vrtSelectedItem
End With
End Sub
Public Sub FP_LateBinding()
Const msoFileDialogFilePicker As Long = 3
Dim fd As Object
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.AllowMultiSelect = True
.Show
For Each vrtSelectedItem In .SelectedItems
Debug.Print vrtSelectedItem
Next vrtSelectedItem
End With
End Sub
--------------EDIT-------------------------
Per comments, I was instructed to not to do this in a class module. I've since edited the code, as below. This still does not allow for the form to appear.
This is in a standard module:
Public Function filePicker() As Variant
Dim fd As FileDialog
Dim sFiles$
Dim vrtSelectedItem As Variant
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.AllowMultiSelect = True
.Show
For Each vrtSelectedItem In .SelectedItems
sFiles = sFiles & vrtSelectedItem & ","
Next vrtSelectedItem '-----------loops again to attach other files
End With
filePicker = sFiles
End Function
I call this procedure from a class module:
Public Sub Test()
Dim vFileList As Variant, vFile As Variant
vFileList = Split(filePicker(), ",")
For Each vFile In vFileList
Attachments.Add vFile
Next vFile
End Sub
-------------FINAL EDIT---------------------
Turns out the problem here was the Access install...I went to the installation directory and found MSACCESS.EXE, right-clicked, and repaired.
What you have looks almost good.
I recommend you ALWAYS, but ALWAYS ALWAYS ALWAYS put option explicit t the start of your code modules.
eg this:
Option Compare Database
Option Explicit
With above, your code does not compile. I mean, after you type in your code, I assume you do from the code menu a debug->compile. I will do that 100's of time in a typical day (after editing code). so, make that a habit.
Also, to set the default for NEW code modules (setting does not effect existing), set this option in the VBA code editor: tools->Options
So, your code snip looks ok, but, with above option explicit, then you have to declare all variables (and bonus is compile will cast incorrect spellings or miss typed variable names).
So, your code like this should work fine:
Public Sub FP_LateBinding()
Const msoFileDialogFilePicker As Long = 3
Dim fd As Object
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.AllowMultiSelect = True
.Show
Dim vrtSelectedItem As Variant
For Each vrtSelectedItem In .SelectedItems
Debug.Print vrtSelectedItem
Next vrtSelectedItem
End With
End Sub
Now, it also not clear how you are test/running the above. But, we assume you create a new code module (NOT class module)
You will then type/paste in above. Save it
do a debug->compile from the VBA editor menu - does it compile ok?
(and if other errors in other places - fix those first).
Now, to run it, place cursor anywhere inside of that code stub, hit f5.
Or you can type in the name of the above sub in the debug window like this:
FP_LateBinding
So your posted code - looks really nice! - all good. Try the above option explcit. And of course now in your example, add the declare for vrtSelectedItem
Edit: ============================================================
Now, of course if this is a class module, then just like code in a form/report, you can't just hit f5 in the code module, nor can you JUST type in the name of the sub.
In fact, if you place your cursor in the code and hit F5, then you get this:
So, it not that the code or file does not appear, you get the above - a big WHOPPER of a difference issue.
And if it is a class module as opposed to a regular code module?
Then to test or use the code, you have to write in test code into a REGULAR code module. You can't use the debug/VBA editor to JUST run a class.
This not different then creating any other object.
So, if we create a new class module, paste in above code, say we save the class code module as MyClassTest ?
Then you have to write code in another standard code module like this:
Sub Test1()
Dim clsTest As New MyClassTest
clsTest.FP_LateBinding
End Sub
So, you can't run class code (even code in a forms code behind module), or any custom class module, you have to FIRST create a instance.
Now it is "possbile" that you used VBA, VB5, VB6. And in fact used "early" versions of Access (before access 2000 - 21 years ago).
you will find by default, it is possbile to use + call class module code without first creating a instance of the class module. This ability was and is for keeping compatibility for pre access 2000 VBA, in which class code modules did not require to be delcared as a new instance. This so called "base" class instance thus was possbile.
it turns out, that if you been importing code for the last 20 years from previous verisons of Access? This flag setting - and option STILL exists in Access 2019 - over 20 years later!!!!
I doubt that you are/did import code from such older versions of Access, but it is in fact still possible to set a class module to NOT require a instance of the class to be created in code. However, I don't recommend doing this, despite the fact that this is still possible.

How to add handwritten signature using Excel's Ink Tools?

I want to add a handwritten digital signature to some of my company's forms.
The goal is to, select a document, add a signature (through the use of a drawing pad, which can be done with Excel's Ink Tools) and store the file in the server as PDF. This would cut out the necessity of printing and then scanning the form back in to obtain a signature.
I'm using Excel for the main interface for file manipulation and search. I've not found any references/libraries for the use of Excel - Ink Tools through VBA.
How do I start Ink Tools Objects in VBA? Would I have to use a different software to get the signature?
Update:
After #Macro Man pointed me in the right direction I found some material that helped get the eSignature up and running.
I've found some material on MSDN Digital Ink Signatures - Concepts and Technologies and InkPicture Class that talk about the Ink collection on VB.net and C# through a PictureBox/Userform , this coupled with the InkEdit Control in another Stackoverflow response in which I realised that VBAs tool box had a InkPicture Control additional control that could be utilized to collect the handwritten eSignature through User form.
Please find below step by step:
In VBAs toolbox on additional control under Tools > Additional Controls there is the InkPicture Control which allows you to create a signature Userform.
Once Added InkPicture can be used as any other control on the toolbox.
Then its a case of initialising the UserForm for the Signature request. I'm using a drawing pad, but other hardware should work as well.
And storing or utilising the resultant image at need, in my case saving a temp version in the server to then resize and add to a Word document.
Edit:
After answering a similar question in here, on how to use Userform InkPicture to input image signature into a worksheet/specific cell, I thought I'd edit this answer for those interested.
The below code will allow you to, open the userform so the user can sign the ink field, save the image temperately, add the InkPicture to your worksheet and kill the temp image.
Set up your UserForm (mine is set up like this, with a couple extra options) the UserForm is named Signature_pad, the essential option you need is Private Sub Use_Click().
This is the code inside the Userform:
Private Sub Use_Click()
'dim object type and byte array to save from binary
Dim objInk As MSINKAUTLib.InkPicture
Dim bytArr() As Byte
Dim File1 As String
'get temp file path as $user\Temp\[file name]
FilePath = Environ$("temp") & "\" & "Signature.png"
' set objInk as image/strokes of InkPicture control form object
Set objInk = Me.SignPicture
'if object is not empty
If objInk.Ink.Strokes.Count > 0 Then
'get bytes from object save
bytArr = objInk.Ink.Save(2)
'create file for output
Open FilePath For Binary As #1
'output/write bytArr into #1/created (empty)file
Put #1, , bytArr
Close #1
End If
'set public File as file path to be used later on main sub
Signature.File = FilePath
Unload Me
End Sub
Private Sub Cancel_Click()
End
End Sub
Private Sub ClearPad_Click()
'delete strokes/lines of signature
Me.SignPicture.Ink.DeleteStrokes
'refresh form
Me.Repaint
End Sub
Below is the Main sub (Module called Signature) to call the userform and handle the signature, you can call this Sub with a button or form another Sub.
'public temp file path
Public File
Sub collect_signature()
'Dim and call userform
Dim myUserForm As Signature_pad
Set myUserForm = New Signature_pad
myUserForm.Show
Set myUserForm = Nothing
'insert image/signature from temp file into application active sheet
Set SignatureImage = Application.ActiveSheet.Shapes.AddPicture(File, False, True, 1, 1, 1, 1)
'scale image/signature
SignatureImage.ScaleHeight 1, True
SignatureImage.ScaleWidth 1, True
'image/signature position
SignatureImage.Top = Range("A1").Top
SignatureImage.Left = Range("A1").Left
'delete temp file
Kill File
End Sub
Be sure to rename either the Userform Name and Buttons Name Or the code to match the names of you buttons.
Is it the InkEdit Control you're after?
This is one of the standard libraries that you can find in Tools->References

Modify Chart properties in Access report via VBA (error 2771)

I am building an Access report (2010 version), and would like to be able to customize it based on the user's selections on a form. When I run it, I get error 2771: The bound or unbound object frame you tried to edit does not contain an OLE object.
This is the code to pass the parameter:
Private Sub Command120_Click()
DoCmd.OpenReport ReportName:="rpt_EODGraph", View:=acViewPreview, _
OpenArgs:=Me!Text0.Value
End Sub
This is the code to open the report.
Private Sub Report_Open(Cancel As Integer)
Dim ch As Chart
Set ch = Me.Graph3.Object.Application.Chart 'This line generates the error
ch.ChartTitle.text = OpenArgs
End Sub
I've found at least one person saying that this is not actually possible to do on a report. (http://www.access-programmers.co.uk/forums/showthread.php?t=177778&page=2 Note that this is page 2 of a 2 page forum discussion...) Can anyone corroborate or refute?
Apparently the Report has to have some kind of focus before OLE objects are accessible.
It is enough if you click on it or set the focus to something:
Private Sub Report_Open(Cancel As Integer)
Dim ch As Object
Me.RandomButton.SetFocus
Set ch = Me.Diagramm11.Object
ch.ChartTitle.Text = "Hello"
End Sub
This works. I just set a button on the report that gets the focus. Perhaps you find something more elegant ;)

Download URL Contents Directly into String (VB6) WITHOUT Saving to Disk

Basically, I want to download the contents of a particular URL (basically, just HTML codes in the form of a String) into my VB6 String variable. However, there are some conditions.
I know about the URLDownloadToFile Function - however, this requires that you save the downloaded file/HTML onto a file location on disk before you can read it into a String variable, this is not an option for me and I do not want to do this.
The other thing is, if I need to use an external library, it must already come with all versions of Windows from XP and onwards, I cannot use a control or library that I am required to ship, package and distribute even if it is free, this is not an option and I do not want to do this. So, I cannot use the MSINET.OCX (Internet Transfer) Control's .OpenURL() function (which simply returns contents into a String), as it does not come with Windows.
Is there a way to be able to do this with the Windows API, URLMON or something else that is pre-loaded into or comes with Windows, or a way to do it in VB6 (SP6) entirely?
If so, I would appreciate direction, because even after one hour of googling, the only examples I've found are references to URLDownloadToFile (which requires saving on disk before being ale to place into a String) and MsInet.OpenURL (which requires that I ship and distribute MSINET.OCX, which I cannot and don't want to do).
Surely there has got to be an elegant way to be able to do this? I can do it in VB.NET without an issue, but obviously don't have the luxury of the .NET framework in VB6 - any ideas?
Update:
I have found this: http://www.freevbcode.com/ShowCode.asp?ID=1252
however it says that the displayed function may not return the entire
page and links to a Microsoft bug report or kb article explaining
this. Also, I understand this is based off wininet.dll - and I'm
wondering which versions of Windows does WinInet.dll come packaged
with? Windows XP & beyond? Does it come with Windows 7 and/or Windows
8?
This is how I did it with VB6 a few years ago:
Private Function GetHTMLSource(ByVal sURL As String) As String
Dim xmlHttp As Object
Set xmlHttp = CreateObject("MSXML2.XmlHttp")
xmlHttp.Open "GET", sURL, False
xmlHttp.send
GetHTMLSource = xmlHttp.responseText
Set xmlHttp = Nothing
End Function
If you want to do this with pure VB, and no IE, then you can take advantage of a little-used features of the VB UserControl - async properties.
Create a new UserControl, and call it something like UrlDownloader. Set the InvisibleAtRuntime property to True. Add the following code to it:
Option Explicit
Private Const m_ksProp_Data As String = "Data"
Private m_bAsync As Boolean
Private m_sURL As String
Public Event AsyncReadProgress(ByRef the_abytData() As Byte)
Public Event AsyncReadComplete(ByRef the_abytData() As Byte)
Public Property Let Async(ByVal the_bValue As Boolean)
m_bAsync = the_bValue
End Property
Public Property Get Async() As Boolean
Async = m_bAsync
End Property
Public Property Let URL(ByVal the_sValue As String)
m_sURL = the_sValue
End Property
Public Property Get URL() As String
URL = m_sURL
End Property
Public Sub Download()
UserControl.AsyncRead m_sURL, vbAsyncTypeByteArray, m_ksProp_Data, IIf(m_bAsync, 0&, vbAsyncReadSynchronousDownload)
End Sub
Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
If AsyncProp.PropertyName = m_ksProp_Data Then
RaiseEvent AsyncReadComplete(AsyncProp.Value)
End If
End Sub
Private Sub UserControl_AsyncReadProgress(AsyncProp As AsyncProperty)
If AsyncProp.PropertyName = m_ksProp_Data Then
Select Case AsyncProp.StatusCode
Case vbAsyncStatusCodeBeginDownloadData, vbAsyncStatusCodeDownloadingData, vbAsyncStatusCodeEndDownloadData
RaiseEvent AsyncReadProgress(AsyncProp.Value)
End Select
End If
End Sub
To use this control, stick it on a form and use the following code:
Option Explicit
Private Sub Command1_Click()
XDownload1.Async = False
XDownload1.URL = "http://www.google.co.uk"
XDownload1.Download
End Sub
Private Sub XDownload1_AsyncReadProgress(the_abytData() As Byte)
Debug.Print StrConv(the_abytData(), vbUnicode)
End Sub
Suffice to say, you can customise this to your hearts content. It can tell (using the AyncProp object) whether the file is cached, and other useful information. It even has a special mode in which you can download GIF, JPG and BMP files and return them as a StdPicture object!
One alternative is using Internet Explorer.
Dim ex As InternetExplorer
Dim hd As HTMLDocument
Dim s As String
Set ex = New InternetExplorer
With ex
.Navigate "http://donttrack.us/"
.Visible = 1
Set hd = .Document
s = hd.body.innerText ' assuming you just want the text
's = hd.body.innerHTML ' if you want the HTML
End With
EDIT: For the above early binding to work you need to set references to "Microsoft Internet Controls" and "Microsoft HTML Object Library" (Tools > References). You could also use late binding, but to be honest, I forget what the proper class names are; maybe someone smart will edit this answer :-)

Close modal form in Access and access module code

I've inherited a Access database that is used to import some data in SQL. The MDB opens with a form, in a modal mode: no Access menubar or buttons are visible. I can view the tables with content by using the Visual Studio 'Data Connection' tool, but I cannot see the module's code.
I've looked at this question here, but the answers there aren't really what I need. Is there a way to either force the form to close (and access the modules) or to extract the VBA code ?
[EDIT] I am using Access 2007, not sure what the original developer used.
Hold down the shift key when you open the database. This will prevent it from loading the automatic script it's running and allow you to gain access to the tables, queries, and VBA scripts.
This is a rather long addendum to, or comment on, Michael Todd's reply in response to edosoft's comment.
Rather than choosing to enable the content, check the start-up options (either (file->options->current database->display form) or (tools->start up->display form)) and remove the name of the form, having taken a note, and ensure that Allow Full Menus (same page) is ticked. You may also like to press Alt+F11 to display code, and check that for start-up code, finally, see if there is an AutoRun macro and rename it.
EDIT re Comments
You do not have to open an mdb to change the start-up form, for example, code such as this could be run from another mdb using the full name and path of the mdb you wish to change.
Sub SetStartForm(DBFile As String)
Dim prp As Object
Dim db As Database
Const PROPERTY_NOT_FOUND As Integer = 3270
Set db = OpenDatabase(DBFile)
db.Properties("StartupForm") = "(none)"
If Err.Number > 0 Then
If Err.Number = PROPERTY_NOT_FOUND Then
'' Create the new property, but this is not relevant in this case
End If
End If
db.Close
Set db = Nothing
Set prp = Nothing
End Sub
Button close with function:
Private sub cmdClose_Click()
CloseForm(YourFormName)
End sub
Public Sub CloseForm(ByVal strFormName As String)
Dim iCounter As Integer
For Each frm In Forms
With frm
If (.Name = strFormName) Then
iCounter = iCounter + 1
End If
End With
Next
If (iCounter > 0) Then
For i = 1 To iCounter
DoCmd.Close acForm, strFormName, acSaveNo
Next
End If
End Sub