Outlook VBA Macro: Best way to indicate 'please wait' - vba

What's the best practice for indicating to the user that a Macro is running within Outlook ?
The macro can take around 1-30 seconds to complete.
I want to avoid a modal 'msgbox' popping up before the macro is run, as this can be annoying.
I would rather avoid the hourglass cursor if possible, and wondered if there was a better way.
Is there a way of placing a non-modal 'status' message up, whilst the macro is running?
(The macro I have runs against the currently selected mailItem - and it launched by a button on the Quick Access Toolbar).

This article (also this) on best practice says use the status bar.
This article on Outlook says:
Changing the Status Bar
There is no
way to change the status bar text in
Microsoft Outlook. The status bar is
not exposed as it is in other
Microsoft Office object models.
Outlook.com provides code for a progress box.

Couple of things that string to mind, I am sure other will have ideas as well.
1.Show a form with a progress bar on it that reports progress or has the progress bar in marque mode if you can’t report progress
2.Show a form with a picture box with your favourite animated gif inside(spinny pizza etc.). You can turn off the buttons etc.
3. Use win api to get play with the outlook staus bar
Not knowing what you are doing in your macro you may have to deal with keeping the form “On top” and pumping async progress into it.
Cheers
Marcus

Expanding on #76mel's answer, a nice way to do this is with a non-modal userform. Make something really simple with just a label and caption like this:
What I like to do is have the userform set as:
Non modal (in properties F4, set ShowModal to false)
This means you can click outside the status bar and it doesn't stop you.
I set the StartupPosition to 0-Manual and Top and Left to something like 100 so that the Status form appears in the top left corner of the screen (out of the way of any other messages which appear in centre by default)
Set the label's value to some default text for when the Userform first loads
Public strStatus As String
Public Const defaultStatus As String = "Default status text" 'set this to whatever you want
Sub statusReporter()
frmStatus.Show
'''
'Your code here
'''
frmStatus.lblStatus = "Step 1"
'...
frmStatus.lblStatus = "Step 2"
'...
'''
'Unload the form
'''
frmStatus.lblStatus = defaultStatus
frmStatus.Hide
End Sub
Note, like with Excel's Application.Statusbar you must reset the userform to its default value if you plan to use it later on in the same instance of Excel
Optionally use this too
'Written By RobDog888 - VB/Office Guru™
'Add a Command Button so you can toggle the userform's topmost effect
Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal wFlags As Long) As Long
Private Const HWND_TOPMOST = -1
Private Const HWND_NOTOPMOST = -2
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private mlHwnd As Long
Private Sub UserForm_Initialize()
Dim overTim As Single
overTim = Timer
mlHwnd = FindWindow("ThunderDFrame", "Status") 'Change "Status" to match your userforms caption
Do While mlHwnd = 0 And Timer - overTim < 5
mlHwnd = FindWindow("ThunderDFrame", "Status")
DoEvents
Loop
'Set topmost
SetWindowPos mlHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE
End Sub
in the userform code itself to keep it on top always

Related

How to prevent the Console window being resized in VB.NET?

I need to prevent the user of my console program from resizing the window, only allowing it to be changed programmatically. If the user changes the width, everything messes up. Also, I want to disable the maximise button. Are either of these possible in Console?
This answer neatly covers how to disable resizing a form in WinForms, but it won't work for Console.
I came up with a solution that prevents re-sizing of a console window application (either by dragging the corner border or by clicking on the maximize or minimize buttons). The following code is written in the form of a complete VB.Net console application (i.e., a Module):
Module Module1
Private Const MF_BYCOMMAND As Integer = &H0
Public Const SC_CLOSE As Integer = &HF060
Public Const SC_MINIMIZE As Integer = &HF020
Public Const SC_MAXIMIZE As Integer = &HF030
Public Const SC_SIZE As Integer = &HF000
Friend Declare Function DeleteMenu Lib "user32.dll" (ByVal hMenu As IntPtr, ByVal nPosition As Integer, ByVal wFlags As Integer) As Integer
Friend Declare Function GetSystemMenu Lib "user32.dll" (hWnd As IntPtr, bRevert As Boolean) As IntPtr
Sub Main()
Dim handle As IntPtr
handle = Process.GetCurrentProcess.MainWindowHandle ' Get the handle to the console window
Dim sysMenu As IntPtr
sysMenu = GetSystemMenu(handle, False) ' Get the handle to the system menu of the console window
If handle <> IntPtr.Zero Then
DeleteMenu(sysMenu, SC_CLOSE, MF_BYCOMMAND) ' To prevent user from closing console window
DeleteMenu(sysMenu, SC_MINIMIZE, MF_BYCOMMAND) 'To prevent user from minimizing console window
DeleteMenu(sysMenu, SC_MAXIMIZE, MF_BYCOMMAND) 'To prevent user from maximizing console window
DeleteMenu(sysMenu, SC_SIZE, MF_BYCOMMAND) 'To prevent the use from re-sizing console window
End If
Do Until (Console.ReadKey.Key = ConsoleKey.Escape)
'This loop keeps the console window open until you press escape
Loop
End Sub
End Module
I based this answer off of the Stack Overflow question/answer: "Disable the maximize and minimize buttons of a c# console [closed]"
Please let me know if this doesn't work for you. Good luck!
I wanted to comment on the accepted answer, but I lack reputation...
To prevent the console window from resizing when you snap it to a corner, you can use
Console.SetBufferSize(80, 24) ' or whatever size you're using...

Button in TextBox disappears when selecting text while Windows media player plays music

This is my first question, please be nice. Oh, and I'm not a native english speaker. :)
I've discovered some weird bug in my application. I've created a TextBox control with a button in it (code below). Following these steps will make the button disappear.
Start windows media player with some music (or video)
Start the application
Click in TextBox control and hold down left mouse button
Move your cursor around like crazy
Woosh... Button disappears.
This will not happen when you closed or paused your windows media player. I was able to reproduce this bug on a different system (Windows 7 and Windows 10). This is totally weird, because it doesn't seem logical. Windows is doing crazy stuff with the windows media player...
I'm not sure if there's a workaround. Can anybody help me with this or should I ask on Microsoft forums? I've tried to call "UpdateButton" while selection changed, but I wasn't successful.
Public Class TextBoxEx
Inherits TextBox
Const BUTTON_WIDTH As Integer = 18
Const EM_SETMARGINS As Integer = &HD3
Const EC_RIGHTMARGIN As Integer = &H2
<Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As IntPtr
End Function
Private btnCommand As Button
Public Sub New()
btnCommand = New Button
btnCommand.Cursor = Cursors.Default
btnCommand.Image = My.Resources.iconCancel
Me.Controls.Add(btnCommand)
Call UpdateButton()
End Sub
Private Sub UpdateButton()
Dim rightMargin As Integer = (BUTTON_WIDTH + 1) << 16
btnCommand.Size = New Size(BUTTON_WIDTH, Me.ClientSize.Height)
btnCommand.Location = New Point(Me.ClientSize.Width - BUTTON_WIDTH, 0)
Call SendMessage(Me.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin)
End Sub
Protected Overrides Sub OnResize(e As System.EventArgs)
MyBase.OnResize(e)
Call UpdateButton()
End Sub
End Class

VB.Net CheckedListBox automatically wrap text

This is more a design than a programming question (well, maybe not). I have 4 CheckedListBoxes which are filled with Data from an SQLite-database (Visual Studio 2010), and some of the entries exceed the width of the Box. I know that I can include a horizontal scrollbar in a CheckedListBox but everybody hates horizontal scrollbars (very ugly), so I tried to find an option to automatically wrap the text that doesn't fit.
So if there is any solution to have the text wrapped when the width of the box is too small it would be awesome.
I could extend the window size but it is already over 1000px in width and some of the users use computers made of wood with 1024x768 solution, so that's not really an option.
Datagrid would be another option but I thought there must be an easier solution. Any hints?
Edit: Sorry, it's Windows Forms.
You can write your own CheckedListBox pretty easily using a panel with some actual CheckBoxes on it so you can do the other things you'd expect such as disable certain ones, fix the way it cuts off drop characters, iterate them and so forth.
The problem with wrap, is a) determining the Text Extent of long text so you know how tall to make each checkbox, and b) having to keep a cumulative items height so you know where to add the next one. Of course, once you support wrap, you have to be able to adjust them all which involves moving them when a text change causes one in the middle to grow/shrink.
The panel's AutoScroll handles all the scrolling for you, which includes adding a HSCroll as needed, which is not always desirable. One way to overcome this, which might work for the actual CheckedListBox you are using, is to eat the HScroll instead.
<DllImport("user32.dll")> _
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Then in form load or perhaps after you have populated it:
ShowScrollBar(myControl.Handle, ScrollBarDirection.SB_HORZ, False)
You can also just subclass the existing CheckedListBox to eat the scrollbar in OnClientSizeChanged
Public Class CheckedListBox2
Inherits CheckedListBox
' optionally remove the scroll bar
Public Property VerticalScrollOnly As Boolean
' PInvokes
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function GetWindowLong(ByVal hWnd As IntPtr,
ByVal nIndex As Integer) As Integer
End Function
<DllImport("user32.dll")>
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) _
As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
'// window style constants for scrollbars
Private Const WS_VSCROLL As Integer = &H200000
Private Const WS_HSCROLL As Integer = &H100000
Private Const GWL_STYLE As Integer = -16
Private Enum ScrollBarDirection
SB_HORZ = 0
SB_VERT = 1
SB_CTL = 2
SB_BOTH = 3
End Enum
' eat the HScroll when it shows up
Protected Overrides Sub OnClientSizeChanged(e As EventArgs)
Dim HScrollVis As Boolean
HScrollVis = IsHScrollVisible(Me)
If VerticalScrollOnly AndAlso HScrollVis Then
ShowScrollBar(MyBase.Handle, ScrollBarDirection.SB_HORZ, False)
End If
MyBase.OnClientSizeChanged(e)
End Sub
Friend Shared Function IsHScrollVisible(ByVal ctl As Control) As Boolean
Dim wndStyle As Integer = GetWindowLong(ctl.Handle, GWL_STYLE)
Return ((wndStyle And WS_HSCROLL) <> 0)
End Function
End Class

VB 2012 TextBox.Clear() not working

I'm working on an encryption program and it uses a "PIN" to calculate some stuff for the encryption. I have a textbox where the user can insert the "PIN". I'd like to prevent people from entering anything but numbers. I added this on the KeyPress event:
If Not Char.IsControl(e.KeyChar) Then
If Not Char.IsNumber(e.KeyChar) Then
MsgBox("Invalid character", , "WARNING!")
TextBox3.Clear()
End If
End If
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox. The typed character appears in the textbox. When I write another one it works the same as before, but it only replaces the last character instead of writing another one. Is there something I'm missing because that looks like a bug to me?
Set the ES_NUMBER windows style for your TextBox:
Public Class Form1
Public Const GWL_STYLE As Integer = (-16)
Public Const ES_NUMBER As Integer = &H2000
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer) As Integer
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer
Public Sub SetNumbersOnlyTextBox(ByVal TB As TextBox)
SetWindowLong(TB.Handle, GWL_STYLE, GetWindowLong(TB.Handle, GWL_STYLE) Or ES_NUMBER)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
SetNumbersOnlyTextBox(TextBox3)
End Sub
End Class
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox.
Yes, that's what modal dialogs do. They block the caller from updates until closed. That's the point; the user cannot interact with the parent until they clear the modal child.
Why not simply clear the textbox first? Better yet; don't show an annoying dialog at all. Simply disallow the user from entering invalid characters by setting e.Handled to true. However, it's a bit trickier than it sounds as you need to allow for the backspace and delete keys, disable pasting, etc.
Here's an example of a NumericTextbox: http://msdn.microsoft.com/en-us/library/ms229644(v=vs.80).aspx
You just need to set the Handled property to true instead of clear:
e.Handled = True
As MarkPM notes above, if its a key you don't want you can set e.handle=true (as you intercept the key on the keypress event) to have the system eat it.
Along with this, in stead of a pop-up, you can have a label on the form that says "Only numbers can be entered here" or something like that. Set it up so that the color of the text is red. Also set it up so the label is not normally visible.
Finally, also in the keypress event, beyond setting e.handle=true for unwanted keys, when an unwanted key comes along make the label that says "Only numbers can be entered here" visible - you can also set up a timed event to turn the label's visibility off after a few seconds. You can also throw a Beep() into the mix if you like :-)
This is less invasive then a pop-up and moves things along nicely for the user.

execute button from command propmpt

I am working on a vb.net project and i have a "start" and "Pause" Buttons on the FormPost.exe
I am trying to schedule a batch process to run every day in the morning at 4:00 AM.
How can i run a command prompt to execuite FormPost.exe and them click on "start" button, all via command prompt?
Please let me know. Thanks
What you can do is this override the OnControlCreateMethod() as follows:
Public Class Form1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
If Environment.CommandLine.Contains("/clickme") Then
ClickMeButton.PerformClick()
// ... need to wait here until click event handler has finished, e.g.
// using synchronization objects
Close()
End If
End Sub
End Class
If you pass "/clickme" on the command line it will execute the click event and then close the form. If the form is the only one in the application it will terminate.
Be aware, though, that you will need to add some logic that waits for the click event handler to finish. Try avoid using polling or sleep. Instead try using synchronization objects.
If this is your application; you can modify the code so that it checks if you are running it from the command line / with appropriate arguments and fire the button click() itself. That'd be the easiest approach (I think John's answer shows this)
If it's not your application; you can still accomplish the same thing, but it's not as pretty. You can write code that will execute the winForm then activate it (to ensure it has focus)
Public Shared Sub ActivateWoW()
Dim myApp As Process = Process.GetProcessesByName("MyApp").First
AppActivate(myApp.Id)
End Sub
Then, you can use SendKeys() to simulate interaction with the form. Let's say the start button takes two 'tab' keys to be selected...
SendKeys.Send("{TAB}{TAB}")
Then a quick pause...
Thread.Sleep(25)
Then hit the enter key (which is almost always just as good as mouse click when the button is selected)
SendKeys.Send("{ENTER}")
If you want to get more involved than that; you need to start using WIN32 API calls. Here is some sample code for a Mouse click...
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Private Const WM_LBUTTONUP As Long = &H202
Private Const WM_RBUTTONUP As Long = &H205
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const WM_RBUTTONDOWN As Long = &H204
Private Shared Function MakeDWord(ByVal LoWord As Integer, ByVal HiWord As Integer) As Long
Return (HiWord * &H10000) Or (LoWord And &HFFFF&)
End Function
Public Shared Sub SendMouseClick()
Dim Wow As Long = FindWindow("GxWindowClass", "MyWindow")
Dim dWord As Long = MakeDWord(LastX - LastRectX, LastY - LastRectY)
SendMessage(Wow, WM_RBUTTONDOWN, 1&, dWord)
Threading.Thread.Sleep(100)
SendMessage(Wow, WM_RBUTTONUP, 1&, dWord)
End Sub