Asynchronous File Downloads from Within VBA (Excel) - vba

I've already tried using many different techniques with this... One that works pretty nicely but still ties up code when running is using the api call:
Private Declare Function URLDownloadToFile Lib "urlmon" _
Alias "URLDownloadToFileA" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
and
IF URLDownloadToFile(0, "URL", "FilePath", 0, 0) Then
End If
I've also used (Successfully) code to write vbscript from within Excel and then running with it wscript and waiting for the callback. But again this isn't totally async and still ties up some of the code.
I'd like to have the files download in an event driven class and the VBA code can do other things in a big loop with "DoEvents". When one file is done it can trigger a flag and the code can process that file while waiting for another.
This is pulling excel files off of an Intranet site. If that helps.
Since I'm sure someone will ask, I can't use anything but VBA. This is going to be used at the workplace and 90% of the computers are shared. I highly doubt they'll spring for the business expense of getting me Visual Studio either. So I have to work with what I have.
Any help would be greatly appreciated.

You can do this using xmlhttp in asynchronous mode and a class to handle its events:
http://www.dailydoseofexcel.com/archives/2006/10/09/async-xmlhttp-calls/
The code there is addressing responseText, but you can adjust that to use .responseBody. Here's a (synchronous) example:
Sub FetchFile(sURL As String, sPath)
Dim oXHTTP As Object
Dim oStream As Object
Set oXHTTP = CreateObject("MSXML2.XMLHTTP")
Set oStream = CreateObject("ADODB.Stream")
Application.StatusBar = "Fetching " & sURL & " as " & sPath
oXHTTP.Open "GET", sURL, False
oXHTTP.send
With oStream
.Type = 1 'adTypeBinary
.Open
.Write oXHTTP.responseBody
.SaveToFile sPath, 2 'adSaveCreateOverWrite
.Close
End With
Set oXHTTP = Nothing
Set oStream = Nothing
Application.StatusBar = False
End Sub

Not sure if this is standard procedure or not but I didn't want to overly clutter my question so people reading it could understand it better.
But I've found an alternate solution to my question that is more in-line with what I was originally requesting. Thanks again to Tim as he set me on the right track, and his use of ADODB.Stream is a vital part of my solution.
This uses the Microsoft WinHTTP Services 5.1 .DLL that should be included with windows in one version or another, if not it is easily downloaded.
I use the following code in a class called "HTTPRequest"
Option Explicit
Private WithEvents HTTP As WinHttpRequest
Private ADStream As ADODB.Stream
Private HTTPRequest As Boolean
Private I As Double
Private SaveP As String
Sub Main(ByVal URL As String)
HTTP.Open "GET", URL, True
HTTP.send
End Sub
Private Sub Class_Initialize()
Set HTTP = New WinHttpRequest
Set ADStream = New ADODB.Stream
End Sub
Private Sub HTTP_OnError(ByVal ErrorNumber As Long, ByVal ErrorDescription As String)
Debug.Print ErrorNumber
Debug.Print ErrorDescription
End Sub
Private Sub HTTP_OnResponseFinished()
'Tim's code Starts'
With ADStream
.Type = 1
.Open
.Write HTTP.responseBody
.SaveToFile SaveP, 2
.Close
End With
'Tim's code Ends'
HTTPRequest = True
End Sub
Private Sub HTTP_OnResponseStart(ByVal Status As Long, ByVal ContentType As String)
End Sub
Private Sub Class_Terminate()
Set HTTP = Nothing
Set ADStream = Nothing
End Sub
Property Get RequestDone() As Boolean
RequestDone = HTTPRequest
End Property
Property Let SavePath(ByVal SavePath As String)
SaveP = SavePath
End Property
The main difference between this and what Tim was describing is that WINHTTPRequest has it's own built in events which I can wrap up in one neat little class and reuse wherever. It's to me, a more elegant solution than calling the XMLHttp and then passing it to a class to wait for it.
Having it wrapped up in a class like this means I can do something along the lines of this..
Dim HTTP(10) As HTTPRequest
Dim URL(2, 10) As String
Dim I As Integer, J As Integer, Z As Integer, X As Integer
While Not J > I
For X = 1 To I
If Not TypeName(HTTP(X)) = "HTTPRequest" And Not URL(2, X) = Empty Then
Set HTTP(X) = New HTTPRequest
HTTP(X).SavePath = URL(2, X)
HTTP(X).Main (URL(1, X))
Z = Z + 1
ElseIf TypeName(HTTP(X)) = "HTTPRequest" Then
If Not HTTP(X).RequestDone Then
Exit For
Else
J = J + 1
Set HTTP(X) = Nothing
End If
End If
Next
DoEvents
Wend
Where I just iterate through URL() with URL(1,N) is the URL and URL(2,N) is the save location.
I admit that can probably be streamlined a bit but it gets the job done for me for now. Just tossing my solution out there for anyone interested.

#TheFuzzyGiggler: +1: Thanks for sharing back.
I know its an old post but perhaps I make someone happy with this addidion to TheFuzzyGigglers code (works only in classes):
I added two properties:
Private pCallBack as string
Private pCallingObject as object
Property Let Callback(ByVal CB_Function As String)
pCallBack = CB_Function
End Property
Property Let CallingObject(set_me As Object)
Set pCallbackObj = set_me
End Property
'and at the end of HTTP_OnResponseFinished()
CallByName pCallbackObj, pCallback, VbMethod
In my class I have
Private EntryCollection As New Collection
Private Sub Download(ByVal fromURL As String, ByVal toPath As String)
Dim HTTPx As HTTPRequest
Dim i As Integer
Set HTTPx = New HTTPRequest
HTTPx.SavePath = toPath
HTTPx.Callback = "HTTPCallBack"
HTTPx.CallingObject = Me
HTTPx.Main fromURL
pHTTPRequestCollection.Add HTTPx
End Sub
Sub HTTPCallBack()
Dim HTTPx As HTTPRequest
Dim i As Integer
For i = pHTTPRequestCollection.Count To 1 Step -1
If pHTTPRequestCollection.Item(i).RequestDone Then pHTTPRequestCollection.Remove i
Next
End Sub
You could access the HTTP object from the HTTPCallBack and do many beautiful things here; the main thing is: its perfectly asynchronous now and easy to use. Hope this helps someone as the OP helped me.
I developed this further into a class: check my blog

Related

VBA - Safely store variable reference

I'm familiar with passing an argument to a procedure by reference. Alternately, ParamArray allows me the flexibility of passing 0 or more arguments to a procedure by reference as well. However, that approach made me wonder if there was a way to preserve a reference to one or more variables beyond the scope of a procedure. My first glimmer of hope was the VBA Array function when I saw it was declared like this:
Array(ParamArray ArgList() As Variant)
So, I put together the following test code:
Private Sub Test()
Dim a As Object
Dim b() As Variant
ParamArrayTest a
Debug.Print TypeName(a) ' Output is 'Dictionary'
b = Array(a) ' b should be like ParamArray ArgList()
Set b(0) = Nothing ' This should clear a
Debug.Print TypeName(a) ' Output is still 'Dictionary'
End Sub
Private Sub ParamArrayTest(ParamArray ArgList() As Variant)
Set ArgList(0) = CreateObject("Scripting.Dictionary")
End Sub
Unfortunately, this did not work as I expected. Despite the argument being passed into the Array function via ParamArray, it would appear that the returned array was by value and not by reference.
Further research led me to the undocumented VBA VarPtr / StrPtr / ObjPtr functions. I found numerous examples of using them in conjunction with the API RtlMoveMemory function. However, all of the articles I read strongly urged against using that approach since it could very easily crash the application. Some of my testing did indeed crash Access.
Another idea I had was to see if I could directly assign a reference of one variable to another:
Private Sub Test()
Dim a As Object
Dim b As Variant
b = ByRef a ' Throws a compiler error
End Sub
Suffice it to say, the compiler simply would not allow that. My question then is, can a variable reference be safely stored / preserved beyond the scope of a procedure (preferably in another variable)?
EDIT
I decided it would be more helpful if I shed some light on what I'm trying to build.
I'm currently in the process of creating a wrapper class which will pass all form / control events to a procedure in one of my modules. It will be used with 2 forms which have the same control structure but connect to different source tables. Bear in mind that the code is incomplete but should be sufficient to illustrate the problem I'm trying to overcome. Also, Database is my VBA project name.
There are four portions to the code:
Form_TEST_FORM - Form Module
Private Sub Form_Open(Cancel As Integer)
FormHub.InitForm Me, Cancel
End Sub
FormHub - Module
Public Sub InitForm( _
ByRef Form As Access.Form, _
ByRef Cancel As Integer _
)
Dim Evt As Database.EventHandler
Set Evt = New Database.EventHandler
Evt.InitFormObject Form, Cancel
FormList.Add Evt, Form.Name
End Sub
Private Function FormList() As VBA.Collection
Static Init As Boolean
Static Coll As VBA.Collection
If Not Init Then
Set Coll = New VBA.Collection
Init = True
End If
Set FormList = Coll
End Function
FormControl - Class Module
Public Ptr As Variant ' Pointer to form control variable
Public acType As Access.AcControlType
EventHandler - Class Module
Private WithEvents Form As Access.Form
Private WithEvents SForm As Access.SubForm
Private CtrlList As VBA.Collection
Private Sub Class_Initialize()
InitCtrlList
End Sub
Public Sub InitFormObject(FormObj As Access.Form, ByRef Cancel As Integer)
Dim ErrFlag As Boolean
Dim Ctrl As Access.Control
Dim FCtrl As Database.FormControl
On Error GoTo Proc_Err
Set Form = FormObj
If Form.Controls.Count <> CtrlList.Count Then
Err.Raise 1, , _
"Form has incorrect number of controls"
End If
' This is where I want to validate the form controls
' and also initialize my event variables.
For Each Ctrl In Form.Controls
If Not CtrlExists(FCtrl, Ctrl.Name) Then
Err.Raise 2, , _
"Invalid control name"
ElseIf FCtrl.acType <> Ctrl.ControlType Then
Err.Raise 3, , _
"Invalid control type"
Else
' Initialize the correct variable with it's
' pointer. This is the part I haven't been
' able to figure out yet.
Set FCtrl.Ptr = Ctrl
End If
Next
Proc_End:
On Error Resume Next
If ErrFlag Then
ClearEventVariables
End If
Set Ctrl = Nothing
Set FCtrl = Nothing
Exit Sub
Proc_Err:
ErrFlag = True
Debug.Print "InitFormObject " & _
"Error " & Err & ": " & Err.Description
Resume Proc_End
End Sub
Private Function CtrlExists( _
ByRef FCtrl As Database.FormControl, _
ByRef CtrlName As String _
) As Boolean
On Error Resume Next
Set FCtrl = CtrlList(CtrlName)
CtrlExists = Err = 0
End Function
Private Sub InitCtrlList()
Set CtrlList = New VBA.Collection
CtrlList.Add SetCtrlData(SForm, acSubform), "SForm"
End Sub
Private Function SetCtrlData( _
ByRef Ctrl As Access.Control, _
ByRef acType As Access.AcControlType _
) As Database.FormControl
Set SetCtrlData = New Database.FormControl
With SetCtrlData
' This assignment is where I need to keep a reference
' to the variable in the class. However, it doesn't
' work.
Set .Ptr = Ctrl
.acType = acType
End With
End Function
Private Sub ClearEventVariables()
Dim FormCtrl As Database.FormControl
Set Form = Nothing
For Each FormCtrl In CtrlList
' Assuming I was able to retain a reference to the
' class variable, this would clear it.
Set FormCtrl.Ptr = Nothing
Next
End Sub
Private Sub Class_Terminate()
ClearEventVariables
Set CtrlList = Nothing
End Sub
I only used 1 control in the code example for simplicity sake. But, the idea is to simplify how much code I would need to modify in order to add / remove controls should the form design change. Or, in the event I have to add more forms to the project.
If you need to reference only within a single module, declare as Public in module header. If you want to reference in any module, declare as Global in a general module header. Even array and recordset and connection objects can be declared this way. Be aware these variables will lose their values if code breaks in runtime.
Or look into TempVar object variables. They don't lose values if code breaks. But can only store number or text values, not objects.

Download multiple files with CefSharp in VB.NET

How can I download multiple files using CefSharp.
I need to download multiple files from a page, I execute a javaScript to do this.
First, I prove in Chrome, and the beginning doesn't work, only download the first link. I fixed it changing the property Automatic Downloads to Allow all sites to download multiple files automatically on Chrome Content Settings-Chrome
With CefSharp, first, I couldn't download anything, with this code I fixed it.
Public Class DownloadHandler
Implements IDownloadHandler
Public Event OnBeforeDownloadFired As EventHandler(Of DownloadItem)
Public Event OnDownloadUpdatedFired As EventHandler(Of DownloadItem)
Public Sub OnBeforeDownload(browser As IBrowser, downloadItem As DownloadItem, callback As IBeforeDownloadCallback) Implements IDownloadHandler.OnBeforeDownload
RaiseEvent OnBeforeDownloadFired(Me, downloadItem)
If Not callback.IsDisposed Then
Using callback
callback.[Continue](downloadItem.SuggestedFileName, showDialog:=False)
End Using
End If
End Sub
Public Sub OnDownloadUpdated(browser As IBrowser, downloadItem As DownloadItem, callback As IDownloadItemCallback) Implements IDownloadHandler.OnDownloadUpdated
RaiseEvent OnDownloadUpdatedFired(Me, downloadItem)
End Sub
End Class
But my problem is it only download the first link, and I need to download multiple files. How can I make CefSharp download multiple files?
What do you mean 'download multiple files from a page?' Are you simply downloading files from a public web site? I can think of a couple ways to do this. If you want to loop through a bunch of links, and download all files, you can setup an inventory list in Excel, like you see in the image below.
Then, run the following Macro.
Private Declare Function URLDownloadToFile Lib "urlmon" Alias _
"URLDownloadToFileA" (ByVal pCaller As Long, ByVal szURL As String, ByVal _
szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Sub DownloadFilefromWeb()
Dim strSavePath As String
Dim URL As String, ext As String
Dim buf, ret As Long
URL = Worksheets("Sheet1").Range("A2").Value
buf = Split(URL, ".")
ext = buf(UBound(buf))
strSavePath = "C:\Users\rshuell\Desktop\Downloads\" & "DownloadedFile." & ext
ret = URLDownloadToFile(0, URL, strSavePath, 0, 0)
If ret = 0 Then
MsgBox "Download has been succeed!"
Else
MsgBox "Error"
End If
End Sub
Now, if you just want to download one single file, run the script below.
Sub DownloadFileWithVBA()
Dim myURL As String
'Right-click on the link named 'Sample Address File'
'Click 'Copy Link Location'
'Paste the link below
myURL = "http://databases.about.com/library/samples/address.xls"
Dim WinHttpReq As Object
Set WinHttpReq = CreateObject("Microsoft.XMLHTTP")
WinHttpReq.Open "GET", myURL, False
WinHttpReq.Send
myURL = WinHttpReq.ResponseBody
Set oStream = CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
oStream.Write WinHttpReq.ResponseBody
oStream.SaveToFile ("C:\Users\Excel\Desktop\address.xls")
oStream.Close
End Sub
This is Excel & VBA, not VB.NET, but if it helps you achieve your objective, please mark it as an answer!

Set icon from generated exe in VB.NET

I'm trying to set the icon from a generated Windows Form Application, here's my code.
Private Sub CompileSourceCode()
Dim cProvider As CodeDomProvider = New VBCodeProvider
Dim cParams As New CompilerParameters
Dim cResult As CompilerResults
Dim sourceCode As String = generate_exe.final_winform
With cParams
.GenerateInMemory = False
.GenerateExecutable = True
.OutputAssembly = "test.exe"
.CompilerOptions = "/target:winexe /win32icon:eye.ico"
.ReferencedAssemblies.AddRange({"System.dll", "System.Windows.Forms.dll", "Microsoft.VisualBasic.dll"})
.MainClass = "MyNamespace.form1"
End With
cResult = cProvider.CompileAssemblyFromSource(cParams, sourceCode)
cProvider.Dispose()
If cResult.Errors.HasErrors Then
MsgBox(cResult.Errors(0).Line.ToString & ", " & cResult.Errors(0).ErrorText)
End If
End Sub
The problem:
The first time i run it, it creates the EXE with the icon i chose.
The second time, if i just change the icon i want to use but leave it as the same OutputAssembly name (test.exe) it creates the EXE but with the old icon, doesn't update.
Edit: Found a very good solution, Although It's been a long time since my question, I think it could help someone else with the same problem using SHChangeNotify. Add that to the top of the code:
Const SHCNE_ASSOCCHANGED As Integer = &H8000000
Const SHCNF_IDLIST As Integer = 0
Private Class NativeMethods
<DllImport("shell32")>
Public Shared Sub SHChangeNotify(ByVal wEventId As Integer, ByVal flags As Integer, ByVal item1 As IntPtr, ByVal item2 As IntPtr)
End Sub
End Class
Usage: (before or after your compile command)
NativeMethods.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, Nothing, Nothing)

Replicate Windows Unhide folders and files function

I'm re-visiting a tool that I wrote in VB.Net for my helpdesk team a while back and want to add a couple of checkboxes to replicate the same function that Windows uses to show hidden files and folders / re-hide, as well as protected operating system files.
I know I can do this by editing a registry entry and restarting explorer.exe, but that closes all open Explorer Windows and I don't want that.
Does anyone know how Windows is able to do this by a simple click of a checkbox and how I may be able to code it in VB.net?
Any input on this is greatly appreciated in advance.
EDIT: So it looks like I have found a refresh method that works to refresh Windows Explorer / File Explorer which can be applied to Drarig's answer below but I am having trouble converting it to VB.net as the original example is in C#.
'Original at http://stackoverflow.com/questions/2488727/refresh-windows-explorer-in-win7
Private Sub refreshExplorer(ByVal explorerType As String)
Dim CLSID_ShellApplication As Guid = Guid.Parse("13709620-C279-11CE-A49E-444553540000")
Dim shellApplicationType As Type = Type.GetTypeFromCLSID(CLSID_ShellApplication, True)
Dim shellApplication As Object = Activator.CreateInstance(shellApplicationType)
Dim windows As Object = shellApplicationType.InvokeMember("Windows", Reflection.BindingFlags.InvokeMethod, Nothing, shellApplication, New Object() {})
Dim windowsType As Type = windows.GetType()
Dim count As Object = windowsType.InvokeMember("Count", Reflection.BindingFlags.GetProperty, Nothing, windows, Nothing)
For i As Integer = 0 To CType(count, Integer)
Dim item As Object = windowsType.InvokeMember("Item", Reflection.BindingFlags.InvokeMethod, Nothing, windows, New Object() {i})
Dim itemType As Type = item.GetType()
'Only fresh Windows explorer Windows
Dim itemName As String = CType(itemType.InvokeMember("Name", Reflection.BindingFlags.GetProperty, Nothing, item, Nothing), String)
If itemName = explorerType Then
itemType.InvokeMember("Refresh", Reflection.BindingFlags.InvokeMethod, Nothing, item, Nothing)
End If
Next
End Sub
I am getting an exception Object reference not set to an instance of an object when I set itemType as Type = item.GetType() above. I can't figure out which object isn't being created. When I step through the code it looks like windowsType contains an object for windows. Does anyone have any idea on this? Once this is worked out I can then apply it to Drarig's solution below.
Alright I wish I could have got this to you sooner, but busy lately at work. I took a little time today to figure this out as I love digging into something I have not done before. This is the whole class from a new project; didn't have time to wrap it up in a separate class. I am sure this will get you what you need. It was a little harder than I thought as getting the correct handle and then send the command, but I got it. I hope you find it useful.
P.S. Some of the things you can leave out, specifically the boolean used for loading, this was so I can pull the current value back on load and either check/uncheck the CheckBox.
Note: This is tried and tested on Windows 7, 8 and 10
Imports Microsoft.Win32
Imports System.Reflection
Imports System.Runtime.InteropServices
Public Class Form1
<Flags()> _
Public Enum KeyboardFlag As UInteger
KEYBOARDF_5 = &H74
End Enum
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindow(ByVal hl As Long, ByVal vm As Long) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function
Private blnLoading As Boolean = False
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
Form1.HideFilesExtension(Me.CheckBox1.Checked)
If Not blnLoading Then NotifyFileAssociationChanged()
RefreshExplorer()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim name As String = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(name, False)
blnLoading = True
Me.CheckBox1.Checked = CBool(key.GetValue("Hidden"))
key.Close()
blnLoading = False
End Sub
Private Shared Sub HideFilesExtension(ByVal Hide As Boolean)
Dim name As String = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(name, True)
key.SetValue("Hidden", If(Hide, 1, 0))
key.Close()
End Sub
Public Shared Sub RefreshExplorer()
Dim clsid As New Guid("13709620-C279-11CE-A49E-444553540000")
Dim typeFromCLSID As Type = Type.GetTypeFromCLSID(clsid, True)
Dim objectValue As Object = Activator.CreateInstance(typeFromCLSID)
Dim obj4 As Object = typeFromCLSID.InvokeMember("Windows", BindingFlags.InvokeMethod, Nothing, objectValue, New Object(0 - 1) {})
Dim type1 As Type = obj4.GetType
Dim obj2 As Object = type1.InvokeMember("Count", BindingFlags.GetProperty, Nothing, obj4, Nothing)
If (CInt(obj2) <> 0) Then
Dim num2 As Integer = (CInt(obj2) - 1)
Dim i As Integer = 0
Do While (i <= num2)
Dim obj5 As Object = type1.InvokeMember("Item", BindingFlags.InvokeMethod, Nothing, obj4, New Object() {i})
Dim type3 As Type = obj5.GetType
Dim str As String = CStr(type3.InvokeMember("Name", BindingFlags.GetProperty, Nothing, obj5, Nothing))
If (str = "File Explorer") Then
type3.InvokeMember("Refresh", BindingFlags.InvokeMethod, Nothing, obj5, Nothing)
End If
i += 1
Loop
End If
End Sub
Public Shared Sub NotifyFileAssociationChanged()
'Find the actual window...
Dim hwnd As IntPtr = FindWindow("Progman", "Program Manager")
'Get the window handle and refresh option...
Dim j = GetWindow(hwnd, 3)
'Finally post the message...
PostMessage(j, 256, KeyboardFlag.KEYBOARDF_5, 3)
End Sub
End Class
Here's a solution for everything excepting the refreshing of the explorer.
I've translated the code, but I'm unable to find how to refresh the explorer/desktop without restarting it.
Const keyName As String = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Const Hidden As String = "Hidden"
Const SHidden As String = "ShowSuperHidden"
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim St As Integer = GetRegValue(Hidden)
If St = 2 Then
SetRegValue(Hidden, 1)
SetRegValue(SHidden, 1)
Else
SetRegValue(Hidden, 2)
SetRegValue(SHidden, 0)
End If
End Sub
Private Function GetRegValue(valueName As String) As Integer
Return CInt(My.Computer.Registry.GetValue(keyName, valueName, 0))
End Function
Private Sub SetRegValue(valueName As String, value As Integer)
My.Computer.Registry.SetValue(keyName, valueName, value, Microsoft.Win32.RegistryValueKind.DWord)
End Sub
I have a few ideas to refresh the desktop :
Send a key to a running process. I tried this (source) :
Dim pp As Process() = Process.GetProcessesByName("explorer")
If pp.Length > 0 Then
For Each p In pp
AppActivate(p.Id)
SendKeys.SendWait("{F5}")
Next
End If
Refresh using SHChangeNotify (source),
Refresh broadcasting a WM_SETTINGCHANGE message (source),
etc.
I think you'll be forced to manually refresh or restart the explorer.

vb.net autocad 2007 selection set has no Items

In OS WIn 7 using Autocad 2007 I try to select items then do stuff
Problem is that there are no ITEMS in the selection set ssetObj - not sure why!
Code: works in vba but not standalone vb.net
Private Sub CommandButton1_Click()
Dim myapp As AcadApplication
Dim mydoc As AcadDocument
Dim ssetObj As AcadSelectionSet
Dim ent As AcadObject
Dim numVertices As Long
On Error GoTo err:
Set myapp = GetObject(, "AutoCAD.Application.17")
Set mydoc = myapp.ActiveDocument
If mydoc.SelectionSets.Count > 0 Then
mydoc.SelectionSets(0).Delete
End If
Set ssetObj = mydoc.SelectionSets.Add("ss")
list1.Clear
Me.Hide
AppActivate ("Autocad")
ssetObj.SelectOnScreen:'WORKS TO SELECT
Dim numpls As Integer
numpls = ssetObj.Count:'WORKS TO GET COUNT
Dim i As Integer
For i = 0 To numpls - 1
Set ent = ssetObj.Item(i)':PROBLEM HERE**THERE ARE NO ITEMS THOUGH COUNT IS CORRECT
If ent.ObjectName = "AcDbLWPolyline" Or ent.ObjectName = "AcDbPolyline" Then
numVertices = (UBound(ent.Coordinates) + 1) / 2
list1.AddItem Str(ent.ObjectID) + "\" + Str(numVertices) + " Vertices"
End If
Next i
Me.Show
Exit Sub
err:
MsgBox err.Description
End Sub
Edit: Further investigation shows that you should be calling ssetObj(i) if you want to get indexed items of your selection set.
I'd not worry about trying to get the count of the selection set anyway if you plan on iterating through it. A For Each should suffice to walk though it. One of the problems with going from VBA/VB6 to VB.NET is the temptation to use the same methodology, when it can sometimes be invalid (at times it can be excellent, but .NET is very capable). Here's my entire class that I tested your problem with, just to show how I'm connecting to AutoCAD and interfacing with it.
Public Class frmMain
Private acApp As AcadApplication
Private polyList As List(Of String)
Const acProgId As String = "AutoCAD.Application.17"
<DllImport("User32.dll")> _
Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
End Function
Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
Try
acApp = DirectCast(Marshal.GetActiveObject(acProgId), AcadApplication)
Catch
Try
Dim acType = Type.GetTypeFromProgID(acProgId)
acApp = DirectCast(Activator.CreateInstance(acType), AcadApplication)
Catch ex As Exception
MsgBox("Unable to create AutoCAD application of type: " & acProgId)
End Try
End Try
End Sub
Private Sub btnSelect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelect.Click
If acApp Is Nothing Then Return
acApp.Visible = True
Dim acDoc As AcadDocument = acApp.ActiveDocument
' Kill all existing selection sets
While (acDoc.SelectionSets.Count > 0)
acDoc.SelectionSets(0).Delete()
End While
Dim mySS As AcadSelectionSet = acDoc.SelectionSets.Add("ss")
SetForegroundWindow(acApp.HWND)
mySS.SelectOnScreen()
polyList = New List(Of String)
Dim numVertices As Integer
For Each ent As AcadEntity In mySS
If ent.ObjectName = "AcDbLWPolyline" Or
ent.ObjectName = "AcDbPolyline" Then
numVertices = (ent.Coordinates.Length) / 2
polyList.Add(String.Format("{0} \ {1} Vertices", ent.ObjectID, numVertices))
End If
Next
End Sub
End Class
External COM methods like this are going to be slower than you're used to seeing via VBA. Therefore it's definitely worth diving into the in-process AutoCAD .NET stuff to see great performance.