How do I play multiple sounds at once? VB .NET - vb.net

I am trying to play multiple sounds at once.
I have tried:
Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpstrCommand As String, ByVal lpstrReturnString As String, _
ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer
'The Sub i am wanting to play the sound in
mciSendString("close myWAV", Nothing, 0, 0)
Dim fileName1 As String = My.Computer.FileSystem.CurrentDirectory + "\Sound\Sound file1.wav"
mciSendString(String.Format("open ""{0}"" type mpegvideo alias myWAV", fileName1), Nothing, 0, 0)
mciSendString("play myWAV", Nothing, 0, 0)
This does nothing at all, is it wrong?
I have also tried:
Dim SoundPlayer(2) As System.Media.SoundPlayer
SoundPlayer(1) = New Media.SoundPlayer(My.Resources.Sound_File_1)
SoundPlayer(2) = New Media.SoundPlayer(My.Resources.Sound_File_2)
SoundPlayer(1).Play
SoundPlayer(2).Play
This plays the second sound but not the first, so it plays sound 1 and then plays sound 2 but does it so fast you dont hear sound 1. So that doesn't work. I have also tried;
My.Computer.Audio.Play(My.Resources.Sound_File_1, AudioPlayMode.Background)
My.Computer.Audio.Play(My.Resources.Sound_File_2, AudioPlayMode.Background)
That does the same as my previous attempt... I have also tried using directX, but this wouldn't work...
Imports Microsoft.DirectX.DirectSound
And directX seems a bit overboard for just trying to play 2 or 3 sounds at once.
I have also tried NAudio, which #Jeremy Thompson recomended (https://markheath.net/post/mixing-and-looping-with-naudio), I conveted it from C# to VB, using https://converter.telerik.com/ But it's not happy with a few parts of the code and i dont know why, here it is:
Public Class LoopStream
Inherits WaveStream
Private sourceStream As WaveStream
Public Sub New(ByVal sourceStream As WaveStream)
Me.sourceStream = sourceStream
Me.EnableLooping = True
End Sub
Public Property EnableLooping As Boolean
Public Overrides ReadOnly Property WaveFormat As WaveFormat
Get
Return sourceStream.WaveFormat
End Get
End Property
Public Overrides ReadOnly Property Length As Long
Get
Return sourceStream.Length
End Get
End Property
Public Overrides Property Position As Long
Get
Return sourceStream.Position
End Get
Set(ByVal value As Long)
sourceStream.Position = value
End Set
End Property
Public Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
Dim totalBytesRead As Integer = 0
While totalBytesRead < count
Dim bytesRead As Integer = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead)
If bytesRead = 0 Then
If sourceStream.Position = 0 OrElse Not EnableLooping Then
Exit While
End If
sourceStream.Position = 0
End If
totalBytesRead += bytesRead
End While
Return totalBytesRead
End Function
End Class
I have also tried this, but "System.Windows.Controls.MediaElement()" Doesnt exist?
Dim m1 As New System.Windows.Controls.MediaElement()
Dim m2 As New System.Windows.Controls.MediaElement()
m1.Source = New Uri(filename1)
m2.Source = New Uri(filename2)
m1.Play()
m2.Play()
Am i doing something wrong with one or more of these methods, or is their a different way to do it that i haven't tried?

Try to use the MediaElement
https://learn.microsoft.com/de-de/dotnet/api/system.windows.controls.mediaelement
it is Easy to use:
Dim m1 As New System.Windows.Controls.MediaElement()
Dim m2 As New System.Windows.Controls.MediaElement()
m1.Source = New Uri(filename1)
m2.Source = New Uri(filename2)
m1.Play()
m2.Play()
Update:
If you use .NET Framework <= 4.8 => Cannot Import System.Windows.Controls
If you use .NET CORE you have to add <UseWPF>true</UseWPF> to your .csproj - file

Related

List and Select Sound Card to Play Sound

I have a school bell project coded with Visual Basic 2010 Express. The computer which runs my program has two or more sound cards. First I will list the sound cards to user. User will select the sound card to work. Finally my program will ring the bells on that sound card. Everything is okey for my codes but i can't list the names of sound cards and ring the bell on specified sound card.
I use WMPLib to play music. I have these codes but there becomes an error "the value is not in the expected range". I spotted where the error is in my codes:
Public Declare Function waveOutGetNumDevs Lib "winmm" () As Integer
Public Declare Function mciSendCommand Lib "winmm.dll" Alias "mciSendCommandA" (ByVal wDeviceID As Integer, ByVal uMessage As String, ByVal dwParam1 As Integer, ByVal dwParam2 As Object) As Integer
Public Declare Function mciGetErrorString Lib "winmm.dll" Alias "mciGetErrorStringA" (ByVal dwError As Integer, ByVal lpstrBuffer As String, ByVal uLength As Integer) As Integer
Public Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer
Public Declare Function mciGetDeviceID Lib "winmm.dll" Alias "mciGetDeviceIDA" (ByVal lpstrName As String) As Integer
Public Const MMSYSERR_NOERROR = 0
Public Const MCI_SET = &H80D
Public Const MCI_WAVE_OUTPUT = &H800000
Public Structure MCI_WAVE_SET_PARMS
Dim dwCallback As Integer
Dim dwTimeFormat As Integer
Dim dwAudio As Integer
Dim wInput As Integer
Dim wOutput As Integer
Dim wFormatTag As Short
Dim wReserved2 As Short
Dim nChannels As Short
Dim wReserved3 As Short
Dim nSamplesPerSec As Integer
Dim nAvgBytesPerSec As Integer
Dim nBlockAlign As Short
Dim wReserved4 As Short
Dim wBitsPerSample As Short
Dim wReserved5 As Short
End Structure
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim parms As MCI_WAVE_SET_PARMS
Dim wDeviceID As Integer
Dim ret As Integer
parms.wOutput = 0
wDeviceID = mciGetDeviceID("waveaudio")
' the value is not in the expected range error is here and it spots parms
ret = mciSendCommand(wDeviceID, MCI_SET, MCI_WAVE_OUTPUT, parms)
If (ret <> MMSYSERR_NOERROR) Then
Stop
End If
If ofd.ShowDialog Then
ret = mciSendString("Open " & Chr(34) & ofd.FileName & Chr(34) & " alias audio", CStr(0), 0, 0)
ret = mciSendString("Open audio", CStr(0), 0, 0)
End If
End Sub
WindowsMedia.Net
You can do this using WindowsMedia.Net library.
The following example is taken from the link below, it is a code blonging to a Windows form and contains the functionality needed to list all available audio devices and choose the default device (the one that will act as sound output).
Set default Wave Out Audio Device - VB.Net / DRVM_MAPPER_PREFERRED_SET
First i will try to split the code into 2 parts:
List available audio devices
Change default audio device
List available devices
Private Sub RefreshInformation()
PopulateDeviceComboBox()
DisplayDefaultWaveOutDevice()
End Sub
Private Sub PopulateDeviceComboBox()
DevicesComboBox.Items.Clear()
' How many wave out devices are there? WaveOutGetNumDevs API call.
Dim waveOutDeviceCount As Integer = waveOut.GetNumDevs()
For i As Integer = 0 To waveOutDeviceCount - 1
Dim caps As New WaveOutCaps
' Get a name - its in a WAVEOUTCAPS structure.
' The name is truncated to 31 chars by the api call. You probably have to
' dig around in the registry to get the full name.
Dim result As Integer = waveOut.GetDevCaps(i, caps, Marshal.SizeOf(caps))
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("GetDevCaps() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
DevicesComboBox.Items.Add(New WaveOutDevice(i, caps))
Next
DevicesComboBox.SelectedIndex = 0
End Sub
Private Sub DisplayDefaultWaveOutDevice()
Dim currentDefault As Integer = GetIdOfDefaultWaveOutDevice()
Dim device As WaveOutDevice = DirectCast(DevicesComboBox.Items(currentDefault), WaveOutDevice)
DefaultDeviceLabel.Text = "Defualt: " & device.WaveOutCaps.szPname
End Sub
Private Function GetIdOfDefaultWaveOutDevice() As Integer
Dim id As Integer = 0
Dim hId As IntPtr
Dim flags As Integer = 0
Dim hFlags As IntPtr
Dim result As Integer
Try
' It would be easier to declare a nice overload with ByRef Integers.
hId = Marshal.AllocHGlobal(4)
hFlags = Marshal.AllocHGlobal(4)
' http://msdn.microsoft.com/en-us/library/bb981557.aspx
result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, hId, hFlags)
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
id = Marshal.ReadInt32(hId)
flags = Marshal.ReadInt32(hFlags)
Finally
Marshal.FreeHGlobal(hId)
Marshal.FreeHGlobal(hFlags)
End Try
' There is only one flag, DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY, defined as 1
' "When the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY flag bit is set, ... blah ...,
' the waveIn and waveOut APIs use only the current preferred device and do not search
' for other available devices if the preferred device is unavailable.
Return id
End Function
Change default device
Private Sub SetDefaultButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SetDefaultButton.Click
If DevicesComboBox.Items.Count = 0 Then Return
Dim selectedDevice As WaveOutDevice = DirectCast(DevicesComboBox.SelectedItem, WaveOutDevice)
SetDefault(selectedDevice.Id)
RefreshInformation()
End Sub
Private Sub SetDefault(ByVal id As Integer)
Dim defaultId As Integer = GetIdOfDefaultWaveOutDevice()
If defaultId = id Then Return ' no change.
Dim result As Integer
' So here we say "change the Id of the device that has id id to 0", which makes it the default.
result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_SET, New IntPtr(id), IntPtr.Zero)
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
End Sub
Full code
Imports MultiMedia
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Public Class Form1
Private DevicesComboBox As New ComboBox
Private DefaultDeviceLabel As New Label
Private WithEvents SetDefaultButton As New Button
Private Const DRVM_MAPPER_PREFERRED_GET As Integer = &H2015
Private Const DRVM_MAPPER_PREFERRED_SET As Integer = &H2016
Private WAVE_MAPPER As New IntPtr(-1)
' This just brings together a device ID and a WaveOutCaps so
' that we can store them in a combobox.
Private Structure WaveOutDevice
Private m_id As Integer
Public Property Id() As Integer
Get
Return m_id
End Get
Set(ByVal value As Integer)
m_id = value
End Set
End Property
Private m_caps As WaveOutCaps
Public Property WaveOutCaps() As WaveOutCaps
Get
Return m_caps
End Get
Set(ByVal value As WaveOutCaps)
m_caps = value
End Set
End Property
Sub New(ByVal id As Integer, ByVal caps As WaveOutCaps)
m_id = id
m_caps = caps
End Sub
Public Overrides Function ToString() As String
Return WaveOutCaps.szPname
End Function
End Structure
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' I do use the IDE for this stuff normally... (in case anyone is wondering)
Me.Controls.AddRange(New Control() {DevicesComboBox, DefaultDeviceLabel, SetDefaultButton})
DevicesComboBox.Location = New Point(5, 5)
DevicesComboBox.DropDownStyle = ComboBoxStyle.DropDownList
DevicesComboBox.Width = Me.ClientSize.Width - 10
DevicesComboBox.Anchor = AnchorStyles.Left Or AnchorStyles.Right
DefaultDeviceLabel.Location = New Point(DevicesComboBox.Left, DevicesComboBox.Bottom + 5)
DefaultDeviceLabel.AutoSize = True
SetDefaultButton.Location = New Point(DefaultDeviceLabel.Left, DefaultDeviceLabel.Bottom + 5)
SetDefaultButton.Text = "Set Default"
SetDefaultButton.AutoSize = True
RefreshInformation()
End Sub
Private Sub RefreshInformation()
PopulateDeviceComboBox()
DisplayDefaultWaveOutDevice()
End Sub
Private Sub PopulateDeviceComboBox()
DevicesComboBox.Items.Clear()
' How many wave out devices are there? WaveOutGetNumDevs API call.
Dim waveOutDeviceCount As Integer = waveOut.GetNumDevs()
For i As Integer = 0 To waveOutDeviceCount - 1
Dim caps As New WaveOutCaps
' Get a name - its in a WAVEOUTCAPS structure.
' The name is truncated to 31 chars by the api call. You probably have to
' dig around in the registry to get the full name.
Dim result As Integer = waveOut.GetDevCaps(i, caps, Marshal.SizeOf(caps))
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("GetDevCaps() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
DevicesComboBox.Items.Add(New WaveOutDevice(i, caps))
Next
DevicesComboBox.SelectedIndex = 0
End Sub
Private Sub DisplayDefaultWaveOutDevice()
Dim currentDefault As Integer = GetIdOfDefaultWaveOutDevice()
Dim device As WaveOutDevice = DirectCast(DevicesComboBox.Items(currentDefault), WaveOutDevice)
DefaultDeviceLabel.Text = "Defualt: " & device.WaveOutCaps.szPname
End Sub
Private Function GetIdOfDefaultWaveOutDevice() As Integer
Dim id As Integer = 0
Dim hId As IntPtr
Dim flags As Integer = 0
Dim hFlags As IntPtr
Dim result As Integer
Try
' It would be easier to declare a nice overload with ByRef Integers.
hId = Marshal.AllocHGlobal(4)
hFlags = Marshal.AllocHGlobal(4)
' http://msdn.microsoft.com/en-us/library/bb981557.aspx
result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, hId, hFlags)
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
id = Marshal.ReadInt32(hId)
flags = Marshal.ReadInt32(hFlags)
Finally
Marshal.FreeHGlobal(hId)
Marshal.FreeHGlobal(hFlags)
End Try
' There is only one flag, DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY, defined as 1
' "When the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY flag bit is set, ... blah ...,
' the waveIn and waveOut APIs use only the current preferred device and do not search
' for other available devices if the preferred device is unavailable.
Return id
End Function
Private Sub SetDefaultButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SetDefaultButton.Click
If DevicesComboBox.Items.Count = 0 Then Return
Dim selectedDevice As WaveOutDevice = DirectCast(DevicesComboBox.SelectedItem, WaveOutDevice)
SetDefault(selectedDevice.Id)
RefreshInformation()
End Sub
Private Sub SetDefault(ByVal id As Integer)
Dim defaultId As Integer = GetIdOfDefaultWaveOutDevice()
If defaultId = id Then Return ' no change.
Dim result As Integer
' So here we say "change the Id of the device that has id id to 0", which makes it the default.
result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_SET, New IntPtr(id), IntPtr.Zero)
If result <> MMSYSERR.NoError Then
Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
End If
End Sub
End Class
System.Management
You can retrieve the available audio devices using System.Management assembly which is a part of .Net framework:
ManagementObjectSearcher mo =
new ManagementObjectSearcher("select * from Win32_SoundDevice");
foreach (ManagementObject soundDevice in mo.Get())
{
String deviceId = soundDevice.GetPropertyValue("DeviceId").ToString();
String name = soundDevice.GetPropertyValue("Name").ToString();
//saving the name and device id in array
}
References
Get list of audio devices and select one using c# (Another solution provided in this link (using Lync 2013 SDK)
Win32_SoundDevice class

VB.NET Display file icons from Network Paths with calling Shell

I have this program that shows files with its icons using a ListView and it works a little bit fine but there's a problem, some files(.exe, .docx etc...) don't show their right icon like this. how do I fix that?
This is how I call the Shell:
' declare the Win32 API function SHGetFileInfo'
Public Declare Auto Function SHGetFileInfo Lib "shell32.dll" (ByVal pszPath As String, ByVal dwFileAttributes As Integer, ByRef psfi As SHFILEINFO, ByVal cbFileInfo As Integer, ByVal uFlags As Integer) As IntPtr
' declare some constants that SHGetFileInfo requires'
Public Const SHGFI_ICON As Integer = &H100
Public Const SHGFI_SMALLICON As Integer = &H1
' define the SHFILEINFO structure'
Structure SHFILEINFO
Public hIcon As IntPtr
Public iIcon As Integer
Public dwAttributes As Integer
<Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> _
Public szDisplayName As String
<Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=80)> _
Public szTypeName As String
End Structure
Function RetrieveShellIcon(ByVal argPath As String) As Image
Dim mShellFileInfo As SHFILEINFO
Dim mSmallImage As IntPtr
Dim mIcon As System.Drawing.Icon
Dim mCompositeImage As Image
mShellFileInfo = New SHFILEINFO
mShellFileInfo.szDisplayName = New String(Chr(0), 260)
mShellFileInfo.szTypeName = New String(Chr(0), 80)
mSmallImage = SHGetFileInfo(argPath, 0, mShellFileInfo, System.Runtime.InteropServices.Marshal.SizeOf(mShellFileInfo), SHGFI_ICON Or SHGFI_SMALLICON)
' create the icon from the icon handle'
Try
mIcon = System.Drawing.Icon.FromHandle(mShellFileInfo.hIcon)
mCompositeImage = mIcon.ToBitmap
Catch ex As Exception
' create a blank black bitmap to return'
mCompositeImage = New Bitmap(16, 16)
End Try
' return the composited image'
Return mCompositeImage
End Function
Function GetIcon(ByVal argFilePath As String) As Image
Dim mFileExtension As String = System.IO.Path.GetExtension(argFilePath)
' add the image if it doesn't exist'
If cIcons.ContainsKey(mFileExtension) = False Then
cIcons.Add(mFileExtension, RetrieveShellIcon(argFilePath))
End If
' return the image'
Return cIcons(mFileExtension)
End Function
and this is how I show file icons in my `ListView.
Sub lv1items()
Dim lvi As ListViewItem
Dim di As New DirectoryInfo(Form2.TextBox1.Text)
Dim exts As New List(Of String)
ImageList1.Images.Clear()
If di.Exists = False Then
MessageBox.Show("Source path is not found", "Directory Not Found", MessageBoxButtons.OK, MessageBoxIcon.Error)
Else
For Each fi As FileInfo In di.EnumerateFiles("*.*")
lvi = New ListViewItem
lvi.Text = fi.Name
lvi.SubItems.Add(((fi.Length / 1024)).ToString("0.00"))
lvi.SubItems.Add(fi.CreationTime.ToShortDateString)
If exts.Contains(fi.Extension) = False Then
Dim mShellIconManager As New Form1
For Each mFilePath As String In My.Computer.FileSystem.GetFiles(Form2.TextBox1.Text)
ImageList1.Images.Add(fi.Extension, GetIcon(mFilePath))
exts.Add(fi.Extension)
Next
End If
lvi.ImageKey = fi.Extension
ListView1.Items.Add(lvi)
Next
End If
End Sub
That appears to be a weird limitation of the .net implication
its really just making a call to shell32.dll
You should call the function in shell32 directly
something like this should work
<DllImport("shell32.dll")>
Private Shared Function ExtractAssociatedIcon(hInst As IntPtr, lpIconPath As StringBuilder, ByRef lpiIcon As UShort) As IntPtr
End Function
_
Dim handle As IntPtr = SafeNativeMethods.ExtractAssociatedIcon(New HandleRef(Nothing, IntPtr.Zero), iconPath, index)
If handle <> IntPtr.Zero Then
Return Icon.FromHandle(handle)
End If
The syntax might not be exactly correct, also there is a good blog post about how to pull that information from the registry (which won't always give you the correct answer, but its faster)
Building a Better ExtractIcon (he uses the SHGetFileInfo API in shell32.dll if that blog ever dies it will give people a place to start looking)

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.

Getting file listings

I am wanting to retrieve all the files in some directories. Here is my original code:
Private Function Search(path As String, Recursive As Boolean) As Boolean
Dim dirInfo As New IO.DirectoryInfo(path)
Dim fileObject As FileSystemInfo
If Recursive = True Then
For Each fileObject In dirInfo.GetFileSystemInfos()
If fileObject.Attributes = FileAttributes.Directory Then
Search(fileObject.FullName, Recursive)
Else
lstFiles.Items.Add(fileObject.FullName)
End If
Next
Else
For Each fileObject In dirInfo.GetFileSystemInfos()
lstFiles.Items.Add(fileObject.FullName)
Next
End If
Return True
End Function
This code works well, yet it returns some directories and I am wanting to only return files.
I tried this code:
Private Sub Search(ByVal path As String, ByVal Recursive As Boolean)
if not Directory.Exists(path) then Exit Sub
Dim initDirInfo As New DirectoryInfo(path)
For Each oFileInfo In initDirInfo.GetFiles
lstfiles.items.add(oFileInfo.Name)
Next
If Recursive Then
For Each oDirInfo In initDirInfo.GetDirectories
Search(oDirInfo.FullName, True)
Next
End If
End Sub
However, i get the following error:
Access to the path 'C:\Users\Simon\AppData\Local\Application Data\' is denied.
Can someone help me with my original code, or help me access these directories with my new code?
thanks
EDIT:
I have added this module to get it working:
Imports System.Security.Principal
Module VistaSecurity
'Declare API
Private Declare Ansi Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As String) As Integer
Private Const BCM_FIRST As Int32 = &H1600
Private Const BCM_SETSHIELD As Int32 = (BCM_FIRST + &HC)
Public Function IsVistaOrHigher() As Boolean
Return Environment.OSVersion.Version.Major < 6
End Function
' Checks if the process is elevated
Public Function IsAdmin() As Boolean
Dim id As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim p As WindowsPrincipal = New WindowsPrincipal(id)
Return p.IsInRole(WindowsBuiltInRole.Administrator)
End Function
' Add a shield icon to a button
Public Sub AddShieldToButton(ByRef b As Button)
b.FlatStyle = FlatStyle.System
SendMessage(b.Handle, BCM_SETSHIELD, 0, &HFFFFFFFF)
End Sub
' Restart the current process with administrator credentials
Public Sub RestartElevated()
Dim startInfo As ProcessStartInfo = New ProcessStartInfo()
startInfo.UseShellExecute = True
startInfo.WorkingDirectory = Environment.CurrentDirectory
startInfo.FileName = Application.ExecutablePath
startInfo.Verb = "runas"
Try
Dim p As Process = Process.Start(startInfo)
Catch ex As Exception
Return 'If cancelled, do nothing
End Try
Application.Exit()
End Sub
End Module
The access denied errors are occurring at Windows Libraries I think. I don't think there is any way to list the file in these libraries as they aren't actually folders.

Generate MD5 and SHA1

I'm using the below function to generate an MD5\SH1 hash for SQL backup files.
This works well, has progress report etc but is slow if using large files.
Could I generate the MD5 at the same time as SH1 rather than having to process the file twice, doubling the time taken? What about converting an MD5 result to SHA1?
Imports System
Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Public Class ASyncFileHashAlgorithm
Protected hashAlgorithm As HashAlgorithm
Protected m_hash As Byte()
Protected cancel As Boolean = False
Protected m_bufferSize As Integer = 4096
Public Delegate Sub FileHashingProgressHandler(ByVal sender As Object, _
ByVal e As FileHashingProgressArgs)
Public Event FileHashingProgress As FileHashingProgressHandler
Public Sub New(ByVal hashAlgorithm As HashAlgorithm)
Me.hashAlgorithm = hashAlgorithm
End Sub
Public Function ComputeHash(ByVal stream As Stream) As Byte()
cancel = False
m_hash = Nothing
Dim _bufferSize As Integer = m_bufferSize
' this makes it impossible to change the buffer size while computing
Dim readAheadBuffer As Byte(), buffer As Byte()
Dim readAheadBytesRead As Integer, bytesRead As Integer
Dim size As Long, totalBytesRead As Long = 0
size = stream.Length
readAheadBuffer = New Byte(_bufferSize - 1) {}
readAheadBytesRead = stream.Read(readAheadBuffer, 0, _
readAheadBuffer.Length)
totalBytesRead += readAheadBytesRead
Do
bytesRead = readAheadBytesRead
buffer = readAheadBuffer
readAheadBuffer = New Byte(_bufferSize - 1) {}
readAheadBytesRead = stream.Read(readAheadBuffer, 0, _
readAheadBuffer.Length)
totalBytesRead += readAheadBytesRead
If readAheadBytesRead = 0 Then
hashAlgorithm.TransformFinalBlock(buffer, 0, bytesRead)
Else
hashAlgorithm.TransformBlock(buffer, 0, bytesRead, buffer, 0)
End If
RaiseEvent FileHashingProgress(Me, New _
FileHashingProgressArgs(totalBytesRead, size))
Loop While readAheadBytesRead <> 0 AndAlso Not cancel
If cancel Then
Return InlineAssignHelper(m_hash, Nothing)
End If
Return InlineAssignHelper(m_hash, hashAlgorithm.Hash)
End Function
Public Property BufferSize() As Integer
Get
Return m_bufferSize
End Get
Set(ByVal value As Integer)
m_bufferSize = value
End Set
End Property
Public ReadOnly Property Hash() As Byte()
Get
Return m_hash
End Get
End Property
'Public Sub Cancel()
' cancel = True
'End Sub
Public Overrides Function ToString() As String
Dim hex As String = ""
For Each b As Byte In Hash
hex += b.ToString("x2")
Next
Return hex
End Function
Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, _
ByVal value As T) As T
target = value
Return value
End Function
End Class
Public Class FileHashingProgressArgs
Inherits EventArgs
Public Property TotalBytesRead() As Long
Get
Return m_TotalBytesRead
End Get
Set(ByVal value As Long)
m_TotalBytesRead = Value
End Set
End Property
Private m_TotalBytesRead As Long
Public Property Size() As Long
Get
Return m_Size
End Get
Set(ByVal value As Long)
m_Size = Value
End Set
End Property
Private m_Size As Long
Public Sub New(ByVal totalBytesRead__1 As Long, ByVal size__2 As Long)
TotalBytesRead = totalBytesRead__1
Size = size__2
End Sub
End Class
The following is how I'm generating a hash using the above:
Shared hasher As New ASyncFileHashAlgorithm(SHA1.Create())
Private Function Test(Byval (strFilePathAndName, as String)
Dim stream As IO.Stream = DirectCast(File.Open(strFilePathAndName, _
FileMode.Open), Stream)
AddHandler hasher.FileHashingProgress, _
AddressOf OnFileHashingProgress
Dim t = New Thread(AddressOf hasher.ComputeHash)
t.Start(stream)
While t.IsAlive
Application.DoEvents()
End While
'LblMD5.Text = hasher.ToString???
LblSHA1.Text = hasher.ToString
stream.Dispose()
End Sub
Public Sub OnFileHashingProgress(ByVal sender As Object, _
ByVal e As FileHashingProgressArgs)
SetControlPropertyValue(uxChildForm.ProgressBar, "Position", _
CInt(e.TotalBytesRead / e.Size * 100))
End Sub
You ToString method may create tremendous overhead for large strings since concatenation is expensive (it creates large temporary buffer) and you do it often. Use a StringBuilder (initialised with the correct size) instead.