Give forms a name with Macro in excel - forms

At the moment I have a project with about 20 forms and sometimes I want to make small adjustments to them. So I created a piece of code to delete the forms and then recreate them the way I want.
The problem is that one line of code keeps giving me Path/File access error (Error 75).
This is a small piece of the code:
Sub makeForm(formName As String)
Dim form As Object
'These lines delete the old form
Set form = ThisWorkbook.VBProject.VBComponents(formName)
ThisWorkbook.VBProject.VBComponents.Remove VBComponent:=form
'This line creates the new form
Set form = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
'These lines give the new form a few properties
With form
.Properties("Name") = formName 'This is the line of code that gives the error
.Properties("Caption") = formName
.Properties("Width") = 320
.Properties("Height") = 242
End With
End Sub
Can someone please tell me how I can make sure this error no longer appears? By the way, this error also appears when I manually want to change the name of a form after this macro failed, but doesn't when I do it before the macro failed.
PS: I'm new to this site so sorry if I made any rookie mistakes.

Saving the workbook after you've removed the userform seems to clear the error.
'These lines delete the old form
Set form = ThisWorkbook.VBProject.VBComponents(formName)
ThisWorkbook.VBProject.VBComponents.Remove VBComponent:=form
Set form = Nothing
ThisWorkbook.Save
I guess Excel refreshes some internal values when it saves.

Related

Quick and easy web automation/data scraping

I need to enter around 3 pieces of data into a website I don't own, press submit, and scrape a few pieces of data. I need to repeat this using different dates around 50 times. I've done this before for stuff I've reused a lot with Powershell. However, I was wondering if there was something simple like point and click I could use as if I spend hours on this I could have just as easily typed in the data and copied the results into a spreadsheet.
Any browser will do.
You can try to use IE automation using VBA to scrape the data from IE and paste it in to Excel Workbook.
Example:
Sub Automate_IE_Load_Page()
'This will load a webpage in IE
Dim i As Long
Dim URL As String
Dim IE As Object
Dim objElement As Object
Dim objCollection As Object
'Create InternetExplorer Object
Set IE = CreateObject("InternetExplorer.Application")
'Set IE.Visible = True to make IE visible, or False for IE to run in the background
IE.Visible = True
'Define URL
URL = "https://www.example.com"
'Navigate to URL
IE.Navigate URL
' Statusbar let's user know website is loading
Application.StatusBar = URL & " is loading. Please wait..."
' Wait while IE loading...
'IE ReadyState = 4 signifies the webpage has loaded (the first loop is set to avoid inadvertently skipping over the second loop)
Do While IE.ReadyState = 4: DoEvents: Loop 'Do While
Do Until IE.ReadyState = 4: DoEvents: Loop 'Do Until
'Webpage Loaded
Application.StatusBar = URL & " Loaded"
'Unload IE
Set IE = Nothing
Set objElement = Nothing
Set objCollection = Nothing
End Sub
References:
(1) Automate Internet Explorer (IE) Using VBA
(2) VBA Web Scraping with GetElementsByTagName
(3) IE (Internet Explorer) Automation using Excel VBA
Further, You can try to modify the code based on your requirement.

Custom events in Access database

I tried this in an Access newgroup, and got over a hundred views, but not a single response, so I hope it fares better here.
I'm trying to get a grip on custom events in VBA. I've scoured endless sources on the net, studied my manuals, experimented with everything I can think of, and success is still somewhat tenuous.
Exactly what I hope to accomplish is not all that complicated, and seems like it should be ideal for custom event routines. I have main forms with comboboxes and listboxes fed from tables or queries. The user can open various dialog boxes and do things to modify the tables. When that activity is done, I would like to requery the box(es) affected by any such activity. The way I have done it in the past was to set a global 'SourceHasChanged' Boolean variable and check its status upon returning from the dialog. It works, but is a bit unwieldy, so I decided to try replacing this with custom events.
Hours of studying and endless dead-end tries and repeats have finally produced the following bits of code. They do nothing spectacular. There is a table called T. The dialog form adds records on each click of the Add button. The main form has another button that deletes all but the first record in the table. Each set of code is supposed to fire an event indicating that the listbox is to be requeried. The code in the main form does okay, but the code in the dialog refuses to activate the StalaSeZmena event. Obviously (I think, anyway), that's because the dialog from creates a new instance of the class module. But I have to have a WithEvents variable in the dialog form. If I don't, I would have to make a reference to the WithEvents variable in the main form. Requiring forms to know that much about each other is exactly back-asswards from what I thought the custom event route was going to accomplish. It would be easier and less confusing to just stay with a global status variable.
Class Module [Zmena]
Public WithEvents Udalost As HlaseniZmeny
Private Sub Udalost_StalaSeZmena()
Form_Mane.lstS.Requery
Debug.Print "Requery via class module"
End Sub
Class Module [HlaseniZmeny]
Public Event StalaSeZmena()
Public Sub OhlasitZmenu()
RaiseEvent StalaSeZmena
End Sub
Regular Module
Public chg As Zmena
Form Code [Mane]
Private WithEvents chgMane As HlaseniZmeny
Private Sub cmdCallDialog_Click()
DoCmd.OpenForm "Dia", acNormal, , , , acDialog
End Sub
Private Sub cmdShrinkT_Click()
CurrentDb.Execute "Delete * From T Where Pole1 <> 'A'"
chgMane.OhlasitZmenu
End Sub
Private Sub Form_Open(Cancel As Integer)
Me.Label1.Caption = Me.SpinButton1.Value
Set chg = New Zmena
Set chgMane = New HlaseniZmeny
Set chg.Udalost = chgMane
End Sub
Private Sub chgMane_StalaSeZmena()
lstS.Requery
Debug.Print "Requery from event on main"
End Sub
Form Code [Dia]
Private WithEvents chgDia As HlaseniZmeny
Private Sub cmdAdd_Click()
CurrentDb.Execute "Insert Into T (Pole1) Values(chr(asc('" & DMax("Pole1", "T", "Pole1") & "')+1))"
Set chgDia = New HlaseniZmeny
Set chg.Udalost = chgDia
chgDia.OhlasitZmenu
Set chgDia = Nothing
End Sub
It's functional, but feels awkward, clunky and not at all intuitive, like scratching my left ear with my right foot. The class module has a reference to the main form, which violates the principle of encapsulation, but I found no way to make the dialog form activate the event routine in the main form. I have to have a global variable to link the two WithEvents variables to, or nothing works, but this also violates encapsulation.
Is this really how these constructs are supposed to operate, or have I accidentally stumbled onto a Mad-Max version that happens to work, but isn't the proper way to build such procedures?

How to display modified body after Outlook ItemLoad

I have code to parse emails and add "tel:" links to phone numbers, but the modified email body doesn't get shown in the Outlook Reading Pane until the user manually reloads it (view another email, come back to this one).
I've tried a few hacks like Inspector.Display, and ActiveExplorer.ClearSelection ActiveExplorer.AddToSelection, but I can't get consistent results (Display will open new Inspectors for some users, very undesirable).
I was also going to investigate hooking the event sooner. Somehow accessing the email body before Outlook renders it, to avoid the need to refresh. I'm very new to VSTO, so I don't know what event would have access to the MailItem but happen after a user selects it and before it is rendered. I have thought about only processing new mail, but that doesn't help when I roll out this addin, only going forward.
Here is my current ItemLoad sub:
Private Sub Application_ItemLoad(Item As Object)
Dim myObj As Outlook.MailItem
Dim ob As Object
ob = GetCurrentItem()
If TypeOf ob Is Outlook.MailItem Then
myObj = ob
Dim oldbody As String = myObj.Body
If myObj.HTMLBody.Length > 0 Then
myObj.HTMLBody = RegularExpressions.Regex.Replace(myObj.HTMLBody, "(?<!tel:)(?<![2-9\.])(?<!\>\ )[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", " ${p1}-${p2}-${p3}")
Else
myObj.Body = RegularExpressions.Regex.Replace(myObj.Body, "(?<!tel:)(?<![2-9\.])[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", "tel:${p1}.${p2}.${p3}")
End If
myObj.Save()
refreshCurrentMessage()
End If
End Sub
GetCurrentItem() just returns either objApp.ActiveExplorer.Selection.Item(1) or objApp.ActiveInspector.CurrentItem based on TypeName(objApp.ActiveWindow)
Outlook doesn't reflect changes made through the OOM immediately. You need to switch to another folder/email to get the item refreshed because there is no way to update the item on the fly.
You can use the CurrentFolder property of the Explorer class which allows to set a Folder object that represents the current folder displayed in the explorer. Thus, the view will be switched to another folder. Then you can set the CurrentFolder folder to initial folder.
Also I'd suggest releasing all underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object.

Making VB Browser Make Facebook Status

I am trying to make a vb script to log into my facebook and update my status. The problem is, the submit button does not have an ID I can find using the page info with Chrome. The script I have so far is:
Try
Dim fileReader As String
fileReader = My.Computer.FileSystem.ReadAllText("pathtotext") ' I sensoored my path
fileReader2 = My.Computer.FileSystem.ReadAllText("pathtotext") 'I sensored my path
WebBrowser1.Document.GetElementById("email").SetAttribute("value", fileReader2)
WebBrowser1.Document.GetElementById("pass").SetAttribute("value", fileReader)
WebBrowser1.Document.GetElementById("loginbutton").InvokeMember("click")
Catch ex As Exception
WebBrowser1.Document.GetElementById("u_0_10").SetAttribute("value", "Can I make a status?")
WebBrowser1.Document.GetElementById("submit").InvokeMember("click")
End Try
All of this works, except the submit button. I used "submit" because that was the type and no ID is showing up. Thanks in advance for all help.
P.S - For future reference, how did you find the id for this button? "If you give a starving man a fish he shall be filled; teach him to fish, however and he shall never go hungry again" - Anonymous
you can use this
Dim htmlElements As HtmlElementCollection = WebBrowser1.Document.All
For Each ClickOnElementButton As HtmlElement In htmlElements
If ClickOnElementButton.InnerText = "Post" Then
ClickOnElementButton.InvokeMember("click")
End If
Next
instead of the old code

Outlook olMailItem.Attachments.Add - does an attachment have to be on the filesystem?

Is there a way to add an attachment to an email you're sending out without the attachment being on the filesystem? From reading over the DOM (http://msdn.microsoft.com/en-us/library/bb175153%28office.12%29.aspx) it says that the attachment source can be either a file path or "an Outlook item that constitutes the attachment". I don't use VBA of the office DOMs all that much and I'm not sure what this is. In addition, all the examples I can find are only giving usage examples by using the filesystem path.
I'm calling this from Word for documents that create themselves by filling out some form fields, then print themselves. I'd like them to go out via E-mail as well, but have no need for a permanent copy of the created file. I do realize that I could save them off to a temp directory, attach the saved file, then delete the file once the mail object is sent. It seems a waste to do this though.
Is there an way I can have Word pass the in-memory Document object off to Outlook to attach to the email?
The answer is no, you cannot attach an in-memory document to an outlook mailitem without saving it to disk first.
Oesor,
I made an assumption that email should get sent automatically, so SendMail method is out.
I tried a couple of things to see whether it would work. In both cases code would be embedded in your Word file.
In pre-2007 Word you could use RoutingSlip functionality:
ActiveDocument.HasRoutingSlip = True 'Creates RoutingSlip
With ActiveDocument.RoutingSlip
.Subject = "email Subject"
.AddRecipient = "recipient#domain.com"
.Delivery = wdAllAtOnce
End With
ActiveDocument.Route
Apparently, this code doesn't work in Word 2007. So instead you can use SendForReview functionality:
ActiveDocument.SendForReview "recipient#domain.com", "email subject", False, True
Email gets sent right away (w/o the popup Outlook window), but a couple of caveats exist: the document has to have a corresponding file - it won't work for a new document that has never been saved, and the first time the recipient opens the attached document from e-mail there may be a popup message about starting the review process.
I hope this helps,
Max.
This should work for you, if the relevant attachment source is an inline item, such as an embedded image. I have not tried it with attached files that are not inline but it may work there too. The basic ideas are:
1) Treat the content of the Emails as a Word document because the native Editor for Outlook is Word.
2) Use Word's Copy and Paste to carry everything around with you via the Clipboard because it is a well tested approach. In the example, I have pasted the new section in at the start in a new Paragraph but you could obviously place it anywhere you want.
The strange thing is, though, (see the Debug.Print) that the Attachments Count in the To document does not change, even though the inline images are all where they should be and can be seen and Sent. Have Outlook fun! (The .olm files in the example are simply Outlook.MailItems that have been saved as Template files. They could just as easily be MailItems from an Outlook folder.)
Private Sub TestAttach()
'Places inline Attachment information into a different MailItem
Dim OlTo As Outlook.MailItem
Dim OlFrom As Outlook.MailItem
Dim DocTo As Word.Document
Dim DocFrom As Word.Document
Dim R As Word.Range
Dim R1 As Word.Range
Dim R2 As Word.Range
Dim lStart As Long
Dim lEnd As Long
Set OlFrom = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeOtherAttachments.oft")
Set OlTo = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeAttachments.oft")
Debug.Print "From file starts with " & OlFrom.Attachments.Count & " attachments."
Debug.Print "To file starts with " & OlTo.Attachments.Count & " attachments."
Set DocFrom = OlFrom.GetInspector.WordEditor
Set DocTo = OlTo.GetInspector.WordEditor
OlFrom.Display
OlTo.Display
Set R2 = DocFrom.Content
With R2.Find 'Note: Find settings are 'sticky' and do not need to be repeated on the next find.
.Forward = True
.Wrap = wdFindStop 'Do not loop back to the start of the document
.Format = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Text = "Start flag for Section with Attachments" 'Find the start of the section to move
.Execute
lStart = R2.Start
.Text = "End flag for Section with Attachments" 'Find the end of the section to move
R2.Collapse wdCollapseEnd
.Execute
lEnd = R2.Start
End With
'OlFrom.Display
Set R2 = DocFrom.Range(lStart, lEnd)
'R2.Select
R2.Copy
Set R = DocTo.Range(1, 1)
R.InsertParagraphBefore
'Place the new inline attachments in the To MailItem
Set R = DocTo.Range(1, 1)
R.Paste
OlTo.Display
Debug.Print OlTo.Attachments.Count; "To file ends with " & OlTo.Attachments.Count & " attachments, the same as the original number but all the inline images show."
End Sub