Synchronize the Scroll position of two Controls with different content - vb.net

I use this simple code to set the position of two Scrollbars of different RichTextBox Controls, at same time.
The trouble comes when the text of a RichTextBox is longer that the other.
Any suggestion? How can I calculate the percentage of the difference, to synchronize the scroll position of the two Controls, e.g., at the start/middle/end, at same time?
Const WM_USER As Integer = &H400
Const EM_GETSCROLLPOS As Integer = WM_USER + 221
Const EM_SETSCROLLPOS As Integer = WM_USER + 222
Declare Function SendMessage Lib "user32.dll" Alias "SendMessageW" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByRef lParam As Point) As Integer
Private Sub RichTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
Dim pt As Point
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, pt)
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, pt)
End Sub
Private Sub RichTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
Dim pt As Point
SendMessage(RichTextBox2.Handle, EM_GETSCROLLPOS, 0, pt)
SendMessage(RichTextBox1.Handle, EM_SETSCROLLPOS, 0, pt)
End Sub

The procedure is described here:
How to scroll a RichTextBox control to a given point regardless of caret position
You need to calculate the maximum Scroll value of your Controls
Consider the ClientSize.Height and the Font.Height: both play a role when we define the maximum scroll position. The max Vertical Scroll Value is defined by:
MaxVerticalScroll = Viewport.Height - ClientSize.Height + Font.Height - BorderSize
where Viewport is the overall internal surface of a Control that includes all its content.
It's often returned by the PreferredSize property (which belongs to the Control class), but, e.g., the RichTextBox, sets the PreferredSize before text wrapping, so it's just relative to the unwrapped text, not really useful here.
You determine the base distance manually (as described in the link above), or use the GetScrollInfo() function. It returns a SCROLLINFO structure that contains the absolute Minimum and Maximum Scroll value and the current Scroll Position.
Calculate the relative difference of the two maximum scroll positions: this is the multiplier factor used to scale the two scroll positions, to generate a common relative value.
Important: using the VScroll event, you have to introduce a variable that prevents the two Control from triggering the Scroll action of the counterpart over and over, causing a StackOverflow exception.
See the VScroll event handler and the use of the synchScroll boolean Field.
▶ The SyncScrollPosition() method calls the GetAbsoluteMaxVScroll() and GetRelativeScrollDiff() methods that calculate the relative scroll values, then calls SendMessage to set the Scroll position of the Control to synchronize.
Both accept TextBoxBase arguments, since RichTextBox derives from this base class, as the TextBox class, so you can use the same methods for both RichTextBox and TextBox Controls without any change.
▶ Use the SendMessage declaration you find here, among the others.
Private synchScroll As Boolean = False
Private Sub richTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
SyncScrollPosition(RichTextBox1, RichTextBox2)
End Sub
Private Sub richTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
SyncScrollPosition(RichTextBox2, RichTextBox1)
End Sub
Private Sub SyncScrollPosition(ctrlSource As TextBoxBase, ctrlDest As TextBoxBase)
If synchScroll Then Return
synchScroll = True
Dim infoSource = GetAbsoluteMaxVScroll(ctrlSource)
Dim infoDest = GetAbsoluteMaxVScroll(ctrlDest)
Dim relScrollDiff As Single = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest)
Dim nPos = If(infoSource.nTrackPos > 0, infoSource.nTrackPos, infoSource.nPos)
Dim pt = New Point(0, CType((nPos + 0.5F) * relScrollDiff, Integer))
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, pt)
synchScroll = False
End Sub
Private Function GetAbsoluteMaxVScroll(ctrl As TextBoxBase) As SCROLLINFO
Dim si = New SCROLLINFO(SBInfoMask.SIF_ALL)
GetScrollInfo(ctrl.Handle, SBParam.SB_VERT, si)
Return si
End Function
Private Function GetRelativeScrollDiff(sourceScrollMax As Integer, destScrollMax As Integer, source As TextBoxBase, dest As TextBoxBase) As Single
Dim border As Single = If(source.BorderStyle = BorderStyle.None, 0F, 1.0F)
Return (CSng(destScrollMax) - dest.ClientSize.Height) / (sourceScrollMax - source.ClientSize.Height - border)
End Function
Win32 methods declarations:
Imports System.Runtime.InteropServices
Private Const WM_USER As Integer = &H400
Private Const EM_GETSCROLLPOS As Integer = WM_USER + 221
Private Const EM_SETSCROLLPOS As Integer = WM_USER + 222
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As Integer, <[In], Out> ByRef lParam As Point) As Integer
End Function
<DllImport("user32.dll")>
Friend Shared Function GetScrollInfo(hwnd As IntPtr, fnBar As SBParam, ByRef lpsi As SCROLLINFO) As Boolean
End Function
<StructLayout(LayoutKind.Sequential)>
Friend Structure SCROLLINFO
Public cbSize As UInteger
Public fMask As SBInfoMask
Public nMin As Integer
Public nMax As Integer
Public nPage As UInteger
Public nPos As Integer
Public nTrackPos As Integer
Public Sub New(mask As SBInfoMask)
cbSize = CType(Marshal.SizeOf(Of SCROLLINFO)(), UInteger)
fMask = mask : nMin = 0 : nMax = 0 : nPage = 0 : nPos = 0 : nTrackPos = 0
End Sub
End Structure
Friend Enum SBInfoMask As UInteger
SIF_RANGE = &H1
SIF_PAGE = &H2
SIF_POS = &H4
SIF_DISABLENOSCROLL = &H8
SIF_TRACKPOS = &H10
SIF_ALL = SIF_RANGE Or SIF_PAGE Or SIF_POS Or SIF_TRACKPOS
SIF_POSRANGE = SIF_RANGE Or SIF_POS Or SIF_PAGE
End Enum
Friend Enum SBParam As Integer
SB_HORZ = &H0
SB_VERT = &H1
SB_CTL = &H2
SB_BOTH = &H3
End Enum
This is how it works:
Note that the two Controls contain different text and also use a different Font:
Segoe UI, 9.75pt the Control above
Microsoft Sans Serif, 9pt the other
C# Version:
private bool synchScroll = false;
private void richTextBox1_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox1, richTextBox2);
}
private void richTextBox2_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox2, richTextBox1);
}
private void SyncScrollPosition(TextBoxBase ctrlSource, TextBoxBase ctrlDest) {
if (synchScroll) return;
synchScroll = true;
var infoSource = GetAbsoluteMaxVScroll(ctrlSource);
var infoDest = GetAbsoluteMaxVScroll(ctrlDest);
float relScrollDiff = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest);
int nPos = infoSource.nTrackPos > 0 ? infoSource.nTrackPos : infoSource.nPos;
var pt = new Point(0, (int)((nPos + 0.5F) * relScrollDiff));
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, ref pt);
synchScroll = false;
}
private SCROLLINFO GetAbsoluteMaxVScroll(TextBoxBase ctrl) {
var si = new SCROLLINFO(SBInfoMask.SIF_ALL);
GetScrollInfo(ctrl.Handle, SBParam.SB_VERT, ref si);
return si;
}
private float GetRelativeScrollDiff(int sourceScrollMax, int destScrollMax, TextBoxBase source, TextBoxBase dest) {
float border = source.BorderStyle == BorderStyle.None ? 0F : 1.0F;
return ((float)destScrollMax - dest.ClientSize.Height) / ((float)sourceScrollMax - source.ClientSize.Height - border);
}
Declarations:
using System.Runtime.InteropServices;
private const int WM_USER = 0x400;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out] ref Point lParam);
[DllImport("user32.dll")]
internal static extern bool GetScrollInfo(IntPtr hwnd, SBParam fnBar, ref SCROLLINFO lpsi);
[StructLayout(LayoutKind.Sequential)]
internal struct SCROLLINFO {
public uint cbSize;
public SBInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
public SCROLLINFO(SBInfoMask mask)
{
cbSize = (uint)Marshal.SizeOf<SCROLLINFO>();
fMask = mask; nMin = 0; nMax = 0; nPage = 0; nPos = 0; nTrackPos = 0;
}
}
internal enum SBInfoMask : uint {
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS,
SIF_POSRANGE = SIF_RANGE | SIF_POS | SIF_PAGE
}
internal enum SBParam : int {
SB_HORZ = 0x0,
SB_VERT = 0x1,
SB_CTL = 0x2,
SB_BOTH = 0x3
}

Related

CoreAudio in vb.net 6.0 Windows 11 : Is there a way to do a ControlChangeNotify callback?

I am writing a volume control app in vb.net 6.0, using a reference to CoreAudioApi.dll.
I can:
Change the volume of the default render and capture devices.
Mute the default devices.
Alter the balance on the render device
List the full names, guid string and status of all the devices
However, despite a lot of research, I can not setup a callback for ControlChangeNotify.
It appears to be simple in C, however I am writing in VB. Can any one suggest a solution?
Update 18/12/2022 #Jimi supplied some very useful definitions and I have edited the post to show the current code attempts. I can get the master default volume using masterVol = GetMasterVolumeObject() as an IAudioEndpointVolume.
Update 22/12/2022 Following a re-read of many postings it became apparent that it was important to create a persistent reference to mastervol and the class reference (MyCallBack) to the class module implementing IAudioEndpointVolumeCallback.
Update 24/12/2022 The AudioCallback class is now firing when volume/mute are changed internally and externally. Transferring the callback data to the main form is problematic. I now have a working system which involves:
Declaring public events in the call back class for each type of control I want to change in the main form.
Initiating a BackgroundWorker to Process the data 10mS after being called by the callback class.
Calculating new audio values from the pNotifyData pointer.
RaiseEvent for each slider/checkbox and label in the main form that needs updating.
AddHandler in the main form to process the callback events.
Create handler routines with delegates for each control type.
From my point of view this topic is solved.
My thanks to #Jimi for his contribution.
Latest Updates:
Option Explicit On
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class AudioCallback
Implements IAudioEndpointVolumeCallback
Public Event LabelReady(sender As AudioCallback, ByVal T As String)
Public Event SliderReady(sender As AudioCallback, EP As String, ByVal T As Integer)
Public Event CheckReady(sender As AudioCallback, EP As String, ByVal T As Boolean)
Public Function OnNotify(pNotifyData As IntPtr) As Integer Implements IAudioEndpointVolumeCallback.OnNotify
' Move to global structure
Gstructure = Marshal.PtrToStructure(pNotifyData, GetType(AUDIO_VOLUME_NOTIFICATION_DATA))
BackGround() ' Asynchronous call and delagate controls
Return 0
End Function
Private Sub BackGround()
Dim bgw = New BackgroundWorker()
AddHandler bgw.DoWork,
Sub()
System.Threading.Thread.Sleep(10)
End Sub
AddHandler bgw.RunWorkerCompleted,
Sub()
Update()
End Sub
bgw.RunWorkerAsync()
End Sub
Private Sub Update()
Dim svolL As Integer
Dim svolR As Integer
Dim balance As Integer
'ChkMaster.Checked = Gstructure.bMuted
RaiseEvent CheckReady(Me, "R", Gstructure.bMuted)
svolL = 0.5 + 100 * Gstructure.Left
svolR = 0.5 + 100 * Gstructure.Right
If svolL = svolR Then
balance = 0
Else
If svolR > svolL Then
balance = 100 - svolL * 100.0 / svolR
Else
balance = -(100 - svolR * 100.0 / svolL)
End If
End If
RaiseEvent SliderReady(Me, "RV", 0.5 + 100 * Gstructure.fMasterVolume) ' HMaster.Value = 0.5 + 100 * Gstructure.fMasterVolume
RaiseEvent SliderReady(Me, "RB", balance) ' HBalance.Value = balance
'LGuid.Text = Gstructure.guidEventContext.ToString
RaiseEvent LabelReady(Me, Gstructure.guidEventContext.ToString)
End Sub
End Class
' Main form Extracts ============================================
Imports CoreAudioApi
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Show()
' events for handling AudioCallback events
AddHandler MyCallBack.LabelReady, AddressOf LReady
AddHandler MyCallBack.SliderReady, AddressOf HReady
AddHandler MyCallBack.CheckReady, AddressOf CReady
End Sub
' Label Guid displays Callback GUID
' CheckBox ChkMaster displays Mute status
' Horizontal scrollbar HMaster displays default render volume
' Horizontal scrollbar HBalance displays default render balance
' The global variable IsSettingMaster prevents code associated
' with altering the value of the sliders from re-setting
' the volume / balance.
Private Sub LReady(sender As AudioCallback, Data As String) ' Set GUID label for Render
UpdateLabel(LGuid, Data)
End Sub
Private Delegate Sub UpdateLabelDelegate(TB As Label, param As String)
Private Sub UpdateLabel(TB As Label, param As String)
If TB.InvokeRequired Then
TB.Invoke(New UpdateLabelDelegate(AddressOf UpdateLabel), New Object() {TB, param})
Else
TB.Text = param
End If
End Sub
Private Sub CReady(sender As AudioCallback, EP As String, Data As Boolean) ' Set Master (Render) Mute checkbox
If EP = "R" Then UpdateChkbox(ChkMaster, Data)
End Sub
Private Delegate Sub UpdateChkboxDelegate(TB As CheckBox, param As Boolean)
Private Sub UpdateChkbox(TB As CheckBox, param As Boolean)
If TB.InvokeRequired Then
TB.Invoke(New UpdateChkboxDelegate(AddressOf UpdateChkbox), New Object() {TB, param})
Else
TB.Checked = param
End If
End Sub
Private Sub HReady(sender As AudioCallback, EP As String, Data As Integer) ' Set Master (Render) Volume / Master Balance slide
IsSettingMaster = True
If EP = "RV" Then UpdateHslide(HMaster, Data)
If EP = "RB" Then UpdateHslide(HBalance, Data)
IsSettingMaster = False
End Sub
Private Delegate Sub UpdateHslideDelegate(TB As HScrollBar, param As Integer)
Private Sub UpdateHslide(TB As HScrollBar, param As Integer)
If TB.InvokeRequired Then
TB.Invoke(New UpdateHslideDelegate(AddressOf UpdateHslide), New Object() {TB, param})
Else
TB.Value = param
End If
End Sub
History:
' ===============================
' Public Class containing Callback:
Option Explicit On
Imports System.Runtime.InteropServices
Public Class AudioCallback
Implements IAudioEndpointVolumeCallback
Public Function OnNotify(pNotifyData As IntPtr) As Integer Implements IAudioEndpointVolumeCallback.OnNotify
' Move to global structure
Gstructure = Marshal.PtrToStructure(pNotifyData, GetType(AUDIO_VOLUME_NOTIFICATION_DATA))
' Need mechanism to process new data in main form
' Must be asynchronous
' Raise event did not work
' Enabling Timer did not work
HaveChange = True ' Flag global variable
Return 0
End Function
End Class
' ===============================
' Public Class to Setup Callback:
Option Explicit On
Imports System.Runtime.InteropServices
' Implements IMMDevice, IMMDeviceEnumerator, IAudioEndpointVolume
' developed from https://exchangetuts.com/how-to-check-if-the-system-audio-is-muted-1641156904496320
Public Class CoreAudio
' Public definition in ModCoreaudioAlt https://stackoverflow.com/questions/52001368/how-to-check-if-the-system-audio-is-muted/52013031#52013031
' Public Interface IAudioEndpointVolumeCallback
' Public Structure AUDIO_VOLUME_NOTIFICATION_DATA
' Public Interface IMMDevice
' Public Interface IMMDeviceEnumerator
' End Public definition in ModCoreaudioAlt ==============================
Dim CLSID_MMDeviceEnumerator As Guid = New Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
Dim MMDeviceEnumeratorType As Type = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, True)
Private hr As Integer
Friend Enum EDataFlow
eRender
eCapture
eAll
EDataFlow_enum_count
End Enum
Friend Enum ERole
eConsole
eMultimedia
eCommunications
ERole_enum_count
End Enum
<Flags>
Friend Enum CLSCTX As UInteger
CLSCTX_INPROC_SERVER = &H1 ' In CLSCTX_ALL
CLSCTX_INPROC_HANDLER = &H2 ' In CLSCTX_ALL
CLSCTX_LOCAL_SERVER = &H4 ' In CLSCTX_ALL
CLSCTX_INPROC_SERVER16 = &H8
CLSCTX_REMOTE_SERVER = &H10 ' In CLSCTX_ALL
CLSCTX_INPROC_HANDLER16 = &H20
CLSCTX_RESERVED1 = &H40
CLSCTX_RESERVED2 = &H80
CLSCTX_RESERVED3 = &H100
CLSCTX_RESERVED4 = &H200
CLSCTX_NO_CODE_DOWNLOAD = &H400
CLSCTX_RESERVED5 = &H800
CLSCTX_NO_CUSTOM_MARSHAL = &H1000
CLSCTX_ENABLE_CODE_DOWNLOAD = &H2000
CLSCTX_NO_FAILURE_LOG = &H4000
CLSCTX_DISABLE_AAA = &H8000
CLSCTX_ENABLE_AAA = &H10000
CLSCTX_FROM_DEFAULT_CONTEXT = &H20000
CLSCTX_ACTIVATE_32_BIT_SERVER = &H40000
CLSCTX_ACTIVATE_64_BIT_SERVER = &H80000
CLSCTX_INPROC = CLSCTX_INPROC_SERVER Or CLSCTX_INPROC_HANDLER
CLSCTX_SERVER = CLSCTX_INPROC_SERVER Or CLSCTX_LOCAL_SERVER Or CLSCTX_REMOTE_SERVER ' In CLSCTX_ALL
CLSCTX_ALL = CLSCTX_SERVER Or CLSCTX_INPROC_HANDLER
End Enum
Friend Function GetMasterVolumeObject() As IAudioEndpointVolume
' Get the default IAudioEndpintVolume as "ppEndpoint" for eRender & eMultimedia
Dim deviceEnumerator As IMMDeviceEnumerator = Nothing
Dim MediaDevice As IMMDevice = Nothing
Dim ppEndpoint As IAudioEndpointVolume = Nothing
Dim EndPointVolID As Guid = GetType(IAudioEndpointVolume).GUID
Try
Dim MMDeviceEnumerator As Object = Activator.CreateInstance(MMDeviceEnumeratorType)
deviceEnumerator = CType(MMDeviceEnumerator, IMMDeviceEnumerator)
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, MediaDevice)
MediaDevice.Activate(EndPointVolID, CLSCTX.CLSCTX_ALL, IntPtr.Zero, ppEndpoint)
Catch ex As Exception
Form1.Showme("Error in GetMasterVolumeObject: " & ex.Message, Color.Red)
ppEndpoint = Nothing
Finally
If Not IsNothing(deviceEnumerator) Then Marshal.ReleaseComObject(deviceEnumerator)
If Not IsNothing(MediaDevice) Then Marshal.ReleaseComObject(MediaDevice)
End Try
Return ppEndpoint
End Function
Public Sub Callback()
Try
masterVol = GetMasterVolumeObject()
' MyCallBack defined in Module ModCoreAudioAlt : Public MyCallBack As New AudioCallback
If IsNothing(MyCallBack) Then
Form1.Showme("Failed to set MyCallBack", Color.Red)
Else
hr = masterVol.RegisterControlChangeNotify(MyCallBack)
If hr <> 0 Then
Form1.Showme("Callback register failed", Color.Red)
If Not IsNothing(masterVol) Then Marshal.ReleaseComObject(masterVol)
Else
Form1.Showme("Callback register OK", Color.Blue)
CallBackOn = True
End If
End If
Catch ex As Exception
Form1.Showme("CallBack error " & ex.Message, Color.Red)
If Not IsNothing(masterVol) Then Marshal.ReleaseComObject(masterVol)
End Try
End Sub
Public Sub Cancelcallback()
If CallBackOn = False Then Exit Sub
hr = masterVol.UnregisterControlChangeNotify(MyCallBack)
If hr <> 0 Then
MsgBox("Callback Failed to UnRegister", vbOK, "Core Audio Callback")
Else
If Not IsNothing(masterVol) Then Marshal.ReleaseComObject(masterVol)
Form1.Showme("Callback Un-register OK", Color.Blue)
CallBackOn = False
End If
End Sub
End Class
' ==============
' Public Module:
Imports System.Runtime.InteropServices
Imports CoreAudioApi
Module ModCoreAudioAlt
' https://stackoverflow.com/questions/74833398/coreaudio-in-vb-net-6-0-windows-11-Is-there-a-way-to-do-a-controlchangenotify
Public CallBackOn As Boolean = False ' Callback is on Flag
Public masterVol As IAudioEndpointVolume = Nothing
Public MyCallBack As New AudioCallback
Public Gstructure As New AUDIO_VOLUME_NOTIFICATION_DATA ' Callback data
Public HaveChange As Boolean = False ' Callback has fired
<ComImport>
<Guid("657804FA-D6AD-4496-8A60-352752AF4F89")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IAudioEndpointVolumeCallback
<PreserveSig()>
Function OnNotify(pNotifyData As IntPtr) As Integer
End Interface
<StructLayout(LayoutKind.Sequential)>
Public Structure AUDIO_VOLUME_NOTIFICATION_DATA
Public guidEventContext As Guid
Public bMuted As Boolean
Public fMasterVolume As Single
Public nChannels As UInteger
Public Left As Single ' .net will not allow pre-dimensioned array (aVolumes(1) as single)
Public Right As Single
End Structure
' https://gist.github.com/sverrirs/d099b34b7f72bb4fb386
<ComImport>
<Guid("5CDF2C82-841E-4546-9722-0CF74078229A")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IAudioEndpointVolume
Function RegisterControlChangeNotify(<MarshalAs(UnmanagedType.Interface)> pNotify As IAudioEndpointVolumeCallback) As Integer
Function UnregisterControlChangeNotify(<MarshalAs(UnmanagedType.Interface)> pNotify As IAudioEndpointVolumeCallback) As Integer
Function GetChannelCount(ByRef channelCount As Integer) As HRESULT
Function SetMasterVolumeLevel() As HRESULT
Function SetMasterVolumeLevelScalar(level As Single, eventContext As Guid) As HRESULT
Function GetMasterVolumeLevel(<Out> ByRef level As Single) As HRESULT
Function GetMasterVolumeLevelScalar(<Out> ByRef level As Single) As HRESULT
Function SetChannelVolumeLevel(channelNumber As Integer, level As Single, eventContext As Guid) As HRESULT
Function SetChannelVolumeLevelScalar(channelNumber As Integer, level As Single, eventContext As Guid) As HRESULT
Function GetChannelVolumeLevel(channelNumber As Integer, <Out> ByRef level As Single) As HRESULT
Function GetChannelVolumeLevelScalar(channelNumber As Integer, <Out> ByRef level As Single) As HRESULT
Function SetMute(<MarshalAs(UnmanagedType.Bool)> isMuted As Boolean, eventContext As Guid) As HRESULT
Function GetMute(<Out> ByRef isMuted As Boolean) As HRESULT
Function GetVolumeStepInfo(<Out> ByRef pnStep As Integer, ByRef pnStepCount As Integer) As HRESULT
Function VolumeStepUp(eventContext As Guid) As HRESULT
Function VolumeStepDown(eventContext As Guid) As HRESULT
Function QueryHardwareSupport(<Out> ByRef hardwareSupportMask As Integer) As HRESULT
Function GetVolumeRange(<Out> ByRef volumeMin As Single, <Out> ByRef volumeMax As Single, <Out> ByRef volumeStep As Single) As HRESULT
End Interface
<ComImport>
<Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IMMDeviceEnumerator
Function EnumAudioEndpoints(ByVal dataFlow As EDataFlow, ByVal dwStateMask As Integer, <Out> ByRef ppDevices As IMMDeviceCollection) As HRESULT
' for 0x80070490 : Element not found
<PreserveSig>
Function GetDefaultAudioEndpoint(ByVal dataFlow As EDataFlow, ByVal role As ERole, <Out> ByRef ppEndpoint As IMMDevice) As HRESULT
Function GetDevice(ByVal pwstrId As String, <Out> ByRef ppDevice As IMMDevice) As HRESULT
Function NotImpl1() As Integer
End Interface
<ComImport>
<Guid("D666063F-1587-4E43-81F1-B948E807363F")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IMMDevice
Function Activate(ByRef iid As Guid, ByVal dwClsCtx As CLSCTX, ByVal pActivationParams As IntPtr, <Out> ByRef ppInterface As IAudioEndpointVolume) As HRESULT
Function OpenPropertyStore(ByVal stgmAccess As Integer, <Out> ByRef ppProperties As IPropertyStore) As HRESULT
Function GetId(<Out> ByRef ppstrId As IntPtr) As HRESULT
Function GetState(<Out> ByRef pdwState As Integer) As HRESULT
End Interface
End Module
' ==========
' Main Form:
Public Class Form1
' Contains:
' HMaster horizonal scroll 0-105 for Volume
' HBalance horizontal scroll -100 to 105 for Balance
' ChkMaster CheckBox for Mute
' LGuid label for Callback GUID
' Uses timer1 with 1 second interval to poll for Callback flag
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If HaveChange = False Then Exit Sub
HaveChange = False
ChkMaster.Checked = Gstructure.bMuted
Dim svolL As Integer = 100 * Gstructure.Left
Dim svolR As Integer = 100 * Gstructure.Right
Dim balance As Integer
If svolL = svolR Then
balance = 0
Else
If svolR > svolL Then
balance = 100 - svolL * 100.0 / svolR
Else
balance = -(100 - svolR * 100.0 / svolL)
End If
End If
HMaster.Value = 100 * Gstructure.fMasterVolume
HBalance.Value = balance
LGuid.Text = (Gstructure.guidEventContext.ToString)
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If CallBackOn Then ClassCoreAudio.Cancelcallback()
End Sub
end class

How do you hide the windows copy dialog box when copying files

How do you hide the windows copy dialog box when copying files through vb.net and just use a progress bar in the vb.net form? This is the code I'm using:
Public Structure SHFILEOPSTRUCT
Public hWnd As Integer
Public wFunc As Integer
Public pFrom As String
Public pTo As String
Public fFlags As Integer
Public fAborted As Integer
Public hNameMaps As Integer
Public sProgress As String
End Structure
Private Declare Function SHFileOperation Lib "shell32.dll" Alias "SHFileOperationA" (ByRef lpFileOp As SHFILEOPSTRUCT) As Integer
Private Const FO_COPY As Int32 = &H2
Private Const FO_DELETE As Int32 = &H3
Private Const FO_MOVE As Int32 = &H1
Private Sub Copy()
Dim shStructure As New SHFILEOPSTRUCT
Dim currentpath As String = lblShare_Path.Text
Dim newpath As String = txtBackupLocation.Text
With shStructure
.wFunc = FO_COPY
.pFrom = currentpath
.pTo = newpath
End With
SHFileOperation(shStructure)
End Sub
Private Sub btnBackup_Click(sender As Object, e As EventArgs) Handles btnBackup.Click
Copy()
progbarStatus.Minimum = 0
progbarStatus.Maximum = DataGridView1.Rows.Count - 1
For i = 0 To DataGridView1.Rows.Count - 1
progbarStatus.Value = i
Next
End Sub
I suggest you to use IO.FileStream to copy file and to get the progress value this way :
Dim CF As New IO.FileStream("C:\[Copy From]", IO.FileMode.Open)
Dim CT As New IO.FileStream("C:\[Copy To]", IO.FileMode.Create)
Dim len As Long = CF.Length - 1
Dim buffer(1024) As Byte
Dim byteCFead As Integer
While CF.Position < len
byteCFead = (CF.Read(buffer, 0, 1024))
CT.Write(buffer, 0, byteCFead)
ProgressBar.Value = CInt(CF.Position / len * 100)
Application.DoEvents()
End While
CT.Flush()
CT.Close()
CF.Close()

VB Windows form will not size to applications width/height

I have a Visual Basic Windows Form size set to be the applications width/height but it's not working.
While this works completely fine for me in VBA, its not working as desired for the AddIn:
Dim newForm As New ExportingForm
newForm.ShowDialog()
Public Class ExportingForm
Private Sub ExportingForm_Layout(sender As Object, e As EventArgs) Handles MyBase.Layout
Dim exclApp As Excel.Application = Globals.ThisAddIn.Application
If exclApp.WindowState = Excel.XlWindowState.xlMaximized Then
Me.WindowState = System.Windows.Forms.FormWindowState.Maximized
Else
Me.Size = New Drawing.Point(exclApp.Width, exclApp.Height)
End If
End Sub
End Class
Additionally in Designer mode here are my settings for the Windows Form:
IsMdiContainer False
Location 0,0
MaximumSize 0,0
MinimumSize 0,0
Padding 0,0,0,0
Size 250,250
StartPosition CenterParent
It centers fine and I can alter the width/height programmatically just fine as well, however, when setting it to the applications width/height it changes to a certain point and stops. What do I need to do to correct this?
I've also tried :
Me.Size = New Drawing.Point(exclApp.ActiveWindow.Width, exclApp.ActiveWindow.Height)
And I've also tried setting the size before showing the form:
Dim newForm.....
newForm.Size = New Drawing.Point(exclApp.Width, exclApp.Height)
newForm.ShowDialog()
I can translate any language you have as long as it works with Visual Studio
Correction
In theory the method I originally presented should work, however there are issues with the Excel PointsToScreenPixels methods. An internet search on the functions indicates that results are unreliable at best. Therefore, I am recommend using the Win32 API function GetWindowRect to retrieve the Excel application's position and size. The API function definitions where obtained from http://www.pinvoke.net/index.aspx.
Imports Excel = Microsoft.Office.Interop.Excel
Imports System.Runtime.InteropServices
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
test()
End Sub
Sub test()
Dim meDPI As PointF = GetDPI(Me.Handle)
Dim app As New Excel.Application
app.Visible = True
Dim appHwnd As IntPtr = New IntPtr(app.Hwnd)
' setting Excel's size and position -- just for test verification purposes
SetWindowPos(appHwnd, IntPtr.Zero, 10, 10, 500, 300, SetWindowPosFlags.DoNotActivate)
Dim rc As RECT
GetWindowRect(appHwnd, rc) ' retrieve Excel's size and position into rc
app.UserControl = True ' return control to the user
Console.WriteLine("Excel located at X: {0}, Y: {1}, Width: {2}, Height: {3}", rc.Left, rc.Top, rc.Width, rc.Height)
Me.Location = rc.Location
Me.Size = rc.Size
Me.Activate() ' bring this form to the front
Me.Opacity = 0.5 ' allow to view thru to Excel
End Sub
Public Shared Function GetDPI(hwnd As IntPtr) As PointF
Dim ret As PointF
Using g As Graphics = Graphics.FromHwnd(hwnd)
ret.X = g.DpiX
ret.Y = g.DpiY
End Using
Return ret
End Function
<DllImport("user32.dll")> _
Private Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure RECT
Private _Left As Integer, _Top As Integer, _Right As Integer, _Bottom As Integer
Public Sub New(ByVal Rectangle As Rectangle)
Me.New(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom)
End Sub
Public Sub New(ByVal Left As Integer, ByVal Top As Integer, ByVal Right As Integer, ByVal Bottom As Integer)
_Left = Left
_Top = Top
_Right = Right
_Bottom = Bottom
End Sub
Public Property X As Integer
Get
Return _Left
End Get
Set(ByVal value As Integer)
_Right = _Right - _Left + value
_Left = value
End Set
End Property
Public Property Y As Integer
Get
Return _Top
End Get
Set(ByVal value As Integer)
_Bottom = _Bottom - _Top + value
_Top = value
End Set
End Property
Public Property Left As Integer
Get
Return _Left
End Get
Set(ByVal value As Integer)
_Left = value
End Set
End Property
Public Property Top As Integer
Get
Return _Top
End Get
Set(ByVal value As Integer)
_Top = value
End Set
End Property
Public Property Right As Integer
Get
Return _Right
End Get
Set(ByVal value As Integer)
_Right = value
End Set
End Property
Public Property Bottom As Integer
Get
Return _Bottom
End Get
Set(ByVal value As Integer)
_Bottom = value
End Set
End Property
Public Property Height() As Integer
Get
Return _Bottom - _Top
End Get
Set(ByVal value As Integer)
_Bottom = value + _Top
End Set
End Property
Public Property Width() As Integer
Get
Return _Right - _Left
End Get
Set(ByVal value As Integer)
_Right = value + _Left
End Set
End Property
Public Property Location() As Point
Get
Return New Point(Left, Top)
End Get
Set(ByVal value As Point)
_Right = _Right - _Left + value.X
_Bottom = _Bottom - _Top + value.Y
_Left = value.X
_Top = value.Y
End Set
End Property
Public Property Size() As Size
Get
Return New Size(Width, Height)
End Get
Set(ByVal value As Size)
_Right = value.Width + _Left
_Bottom = value.Height + _Top
End Set
End Property
Public Shared Widening Operator CType(ByVal Rectangle As RECT) As Rectangle
Return New Rectangle(Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height)
End Operator
Public Shared Widening Operator CType(ByVal Rectangle As Rectangle) As RECT
Return New RECT(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom)
End Operator
Public Shared Operator =(ByVal Rectangle1 As RECT, ByVal Rectangle2 As RECT) As Boolean
Return Rectangle1.Equals(Rectangle2)
End Operator
Public Shared Operator <>(ByVal Rectangle1 As RECT, ByVal Rectangle2 As RECT) As Boolean
Return Not Rectangle1.Equals(Rectangle2)
End Operator
Public Overrides Function ToString() As String
Return "{Left: " & _Left & "; " & "Top: " & _Top & "; Right: " & _Right & "; Bottom: " & _Bottom & "}"
End Function
Public Overloads Function Equals(ByVal Rectangle As RECT) As Boolean
Return Rectangle.Left = _Left AndAlso Rectangle.Top = _Top AndAlso Rectangle.Right = _Right AndAlso Rectangle.Bottom = _Bottom
End Function
Public Overloads Overrides Function Equals(ByVal [Object] As Object) As Boolean
If TypeOf [Object] Is RECT Then
Return Equals(DirectCast([Object], RECT))
ElseIf TypeOf [Object] Is Rectangle Then
Return Equals(New RECT(DirectCast([Object], Rectangle)))
End If
Return False
End Function
End Structure
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As SetWindowPosFlags) As Boolean
End Function
<Flags> _
Private Enum SetWindowPosFlags As UInteger
''' <summary>If the calling thread and the thread that owns the window are attached to different input queues,
''' the system posts the request to the thread that owns the window. This prevents the calling thread from
''' blocking its execution while other threads process the request.</summary>
''' <remarks>SWP_ASYNCWINDOWPOS</remarks>
ASynchronousWindowPosition = &H4000
''' <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
''' <remarks>SWP_DEFERERASE</remarks>
DeferErase = &H2000
''' <summary>Draws a frame (defined in the window's class description) around the window.</summary>
''' <remarks>SWP_DRAWFRAME</remarks>
DrawFrame = &H20
''' <summary>Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to
''' the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE
''' is sent only when the window's size is being changed.</summary>
''' <remarks>SWP_FRAMECHANGED</remarks>
FrameChanged = &H20
''' <summary>Hides the window.</summary>
''' <remarks>SWP_HIDEWINDOW</remarks>
HideWindow = &H80
''' <summary>Does not activate the window. If this flag is not set, the window is activated and moved to the
''' top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
''' parameter).</summary>
''' <remarks>SWP_NOACTIVATE</remarks>
DoNotActivate = &H10
''' <summary>Discards the entire contents of the client area. If this flag is not specified, the valid
''' contents of the client area are saved and copied back into the client area after the window is sized or
''' repositioned.</summary>
''' <remarks>SWP_NOCOPYBITS</remarks>
DoNotCopyBits = &H100
''' <summary>Retains the current position (ignores X and Y parameters).</summary>
''' <remarks>SWP_NOMOVE</remarks>
IgnoreMove = &H2
''' <summary>Does not change the owner window's position in the Z order.</summary>
''' <remarks>SWP_NOOWNERZORDER</remarks>
DoNotChangeOwnerZOrder = &H200
''' <summary>Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to
''' the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent
''' window uncovered as a result of the window being moved. When this flag is set, the application must
''' explicitly invalidate or redraw any parts of the window and parent window that need redrawing.</summary>
''' <remarks>SWP_NOREDRAW</remarks>
DoNotRedraw = &H8
''' <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
''' <remarks>SWP_NOREPOSITION</remarks>
DoNotReposition = &H200
''' <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
''' <remarks>SWP_NOSENDCHANGING</remarks>
DoNotSendChangingEvent = &H400
''' <summary>Retains the current size (ignores the cx and cy parameters).</summary>
''' <remarks>SWP_NOSIZE</remarks>
IgnoreResize = &H1
''' <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
''' <remarks>SWP_NOZORDER</remarks>
IgnoreZOrder = &H4
''' <summary>Displays the window.</summary>
''' <remarks>SWP_SHOWWINDOW</remarks>
ShowWindow = &H40
End Enum
End Class
Please note that in testing the above code, the WinForm application is declared to be DPI aware by having the following in its app.Manifest file.
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
Do Not Use
The Application.Height Property and Application.Width Property is measured in points not pixels. You can use the Window.PointsToScreenPixelsX Method and the Window.PointsToScreenPixelsY methods to compute the width and height in pixels to set your form size.
width = exclApp.ActiveWindow.PointsToScreenPixelsX(exclApp.Width)
height = exclApp.ActiveWindow.PointsToScreenPixelsY(exclApp.Height)
I do not know if you will also have to declare your addin as DPI aware to avoid Windows's scaling your form.
Note: Base on testing in Excel, only the ActiveWindow will yield a value.

application using graphics, only displays red X's over main window and control

the code i have that interacts with e.graphics is
'clear area
e.Graphics.FillRectangle(Brushes.Black, 0, 0, 600, 800)
'draw sand
For Each i In world
e.Graphics.FillRectangle(i.getcolor, i.getx, i.gety, 1, 1)
Next
here are the variable areas of my two classes, along with their new functions; assume that the get lines do what you think they do.
Public Class Sand
Private x As Integer
Private y As Integer
Private type As element
Public Sub New(ByVal x As Integer, ByVal y As Integer, ByVal type As element)
Me.x = x
Me.y = y
Me.type = type
End Sub
Public Class element
Public color As Color
Public weight As Integer = 1
Public spread As Double = 0.5
Public text As String = "null"
Public Sub New(ByVal c As Color, ByVal w As Integer, ByVal s As Double, ByVal t As String)
color = c
weight = w
spread = s
text = t
End Sub
here is the spot in my program that dimensions the world variable and such.
Private world As List(Of Sand)
Private paused As Boolean = False
Private openbottom As Boolean = False
Private selected As Integer = 0
Private elements As List(Of element)
'DEBUG VARIABLES
Private debugmode As Boolean = True
Private framenum As Integer = 0
and here is the area in my debug portion that makes a 4 grains of sand in 2 different elements.
elements.Add(New element(Color.Azure, 1, 1, "Test El 1"))
elements.Add(New element(Color.Aqua, 1, 1, "Test El 2"))
world.Add(New Sand(240, 400, elements(1)))
world.Add(New Sand(440, 200, elements(1)))
world.Add(New Sand(340, 100, elements(2)))
world.Add(New Sand(540, 400, elements(1)))
A red x's over the main window means that your custom drawing code launches an exception. Is world null? Is some i null?

listview tile layout problem (vb.net)

I have a listview which displays (eventually) an album cover of an itunes play list with the album name under it. the problem I am having is that I cannot get the album art (currently a blank square) ABOVE the album name. It always is on the side... how do I do it? I've tried adding column headers and alsorts...
code to set up the listview
Dim myImageList As ImageList
albumList.View = View.Tile
albumList.TileSize = New Size(120, 150)
' Initialize the item icons.
myImageList = New ImageList()
myImageList.Images.Add(Image.FromFile("c:/test.jpg"))
myImageList.ImageSize = New Size(80, 80)
albumList.LargeImageList = myImageList
I then do a loop to display each album name which uses
Dim item0 As New ListViewItem(New String() _
{Albums(i).Name}, 0)
albumList.Items.Add(item0)
the output is http://i111.photobucket.com/albums/n122/mfacer/Screenshot2010-05-02at164815.png
but as i said, I want the album name under the orange box....
any ideas??
Thanks for any info!
That is the baked-in arrangement for tile view. If you want the labels underneath the images then you have to set View = LargeIcon. If that produces an undesirable spacing of images then you can P/Invoke SendMessage() to send the LVM_SETICONSPACING message. This worked well:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class TileView : ListView {
public TileView() {
mSpacing = new Size(48, 48);
}
private Size mSpacing;
public Size IconSpacing {
get { return mSpacing; }
set {
mSpacing = value;
updateSpacing();
}
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
updateSpacing();
}
private void updateSpacing() {
if (this.IsHandleCreated) {
SendMessage(this.Handle, 0x1000 + 53, IntPtr.Zero, (IntPtr)((mSpacing.Height << 16) | mSpacing.Width));
}
}
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
Change the new IconSpacing property in the designer to work well with the size of the images in your ImageList. You'll see the effect immediately.
Public Class TileView
Inherits ListView
Public Sub New()
mSpacing = New Size(48, 48)
End Sub
Private mSpacing As Size
Public Property IconSpacing As Size
Get
Return mSpacing
End Get
Set(ByVal value As Size)
mSpacing = value
updateSpacing()
End Set
End Property
Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
MyBase.OnHandleCreated(e)
updateSpacing()
End Sub
Private Sub updateSpacing()
If Me.IsHandleCreated Then
SendMessageW(Me.Handle, &H1000 + 53, IntPtr.Zero, CType((mSpacing.Height << 16) Or mSpacing.Width, IntPtr))
End If
End Sub
Private Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wp As IntPtr, ByVal lp As IntPtr) As IntPtr
End Class