Showing posts with label office. Show all posts
Showing posts with label office. Show all posts

Friday, May 16, 2008

Minimize userform to system tray

During the course of any VBA developer's life, the desire to want to be able to make a form seem independent of Excel will appear. One nice way to do this is to hide the excel application, so only a userform appears, but it doesnt usually seem the same. One nice thing that can be done to improve the looks (and possible functionality) is to minimize a userform to the system tray. An actual need to do this rarely comes up, but is usually impressive anyways depending on the end user.

I would like to say for the record that I did not create this code. I found some snippets that others had written in the past, and adapted it for my example. As usual, I researched exactly what it is doing so that I can write something similar from scratch as well as help others with any problems they have doing the same. I really don't remember who wrote the initial code that I adapted this from though. If you know, please let me know so I can give them proper credit.

Anyways, this is a pretty simple concept. Create a userform (called UserForm1), and add 2 commandbuttons to it. Keep them at their default name as well. Inside the codepane for the userform, paste the following:

'******************************************************'
'**************** START USERFORM1 CODE ****************'
'******************************************************'
Option Explicit
Private Sub CommandButton1_Click()
Dim Me_hWnd As Long, Me_Icon As Long, Me_Icon_Handle As Long, IconPath As String
Me_hWnd = FindWindowd("ThunderDFrame", UserForm1.Caption)
IconPath = Application.Path & Application.PathSeparator & "excel.exe"
Me_Icon_Handle = ExtractIcond(0, IconPath, 0)
Hook Me_hWnd
AddIconToTray Me_hWnd, 0, Me_Icon_Handle, "Double Click to re-open userform"
Me.Hide
End Sub

Private Sub CommandButton2_Click()
Application.Visible = True
Unload Me
End Sub

Private Sub UserForm_Activate()
RemoveIconFromTray
Unhook
End Sub

Private Sub UserForm_Initialize()
CommandButton1.Caption = "Minimize to tray"
CommandButton2.Caption = "Close this form"
Application.Visible = False
End Sub
'******************************************************'
'***************** END USERFORM1 CODE *****************'
'******************************************************'


Then insert a new module in the same project, and paste in the following:
'******************************************************'
'***************** START MODULE CODE ******************'
'******************************************************'
Option Explicit
Declare Function SetForegroundWindow Lib "User32" (ByVal hwnd As Long) As Long
Declare Function Shell_NotifyIcon Lib "shell32.dll" Alias "Shell_NotifyIconA" ( _
ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long
Declare Function CallWindowProc Lib "User32" Alias "CallWindowProcA" (ByVal _
lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam _
As Long, ByVal lParam As Long) As Long
Declare Function SetWindowLong Lib "User32" Alias "SetWindowLongA" (ByVal hwnd _
As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Declare Function FindWindow Lib "User32" Alias "FindWindowA" (ByVal lpClassName _
As String, ByVal lpWindowName As String) As Long
Declare Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" (ByVal hInst _
As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long

Public Const WM_LBUTTONDOWN = &H201
Public Const WM_LBUTTONUP = &H202
Public Const WM_LBUTTONDBL = &H203
Public Const WM_RBUTTONDOWN = &H204
Public Const WM_RBUTTONUP = &H205
Public Const WM_ACTIVATEAPP = &H1C
Public Const NIF_ICON = &H2
Public Const NIF_MESSAGE = &H1
Public Const NIF_TIP = &H4
Public Const NIM_ADD = &H0
Public Const NIM_DELETE = &H2
Public Const MAX_TOOLTIP As Integer = 64
Public Const GWL_WNDPROC = (-4)

Type NOTIFYICONDATA
cbSize As Long
hwnd As Long
uID As Long
uFlags As Long
uCallbackMessage As Long
hIcon As Long
szTip As String * MAX_TOOLTIP
End Type

Public nfIconData As NOTIFYICONDATA

Private FHandle As Long
Private WndProc As Long
Private Hooking As Boolean

Public Sub Hook(Lwnd As Long)
If Hooking = False Then
FHandle = Lwnd
WndProc = SetWindowLong(Lwnd, GWL_WNDPROC, AddressOf WindowProc)
Hooking = True
End If
End Sub

Public Sub Unhook()
If Hooking = True Then
SetWindowLong FHandle, GWL_WNDPROC, WndProc
Hooking = False
End If
End Sub

Public Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam _
As Long, ByVal lParam As Long) As Long
If Hooking Then
If lParam = WM_LBUTTONDBL Then
UserForm1.Show 1
WindowProc = True
' Unhook
Exit Function
End If
WindowProc = CallWindowProc(WndProc, hw, uMsg, wParam, lParam)
End If
End Function

Public Sub RemoveIconFromTray()
Shell_NotifyIcon NIM_DELETE, nfIconData
End Sub

Public Sub AddIconToTray(MeHwnd As Long, MeIcon As Long, MeIconHandle As Long, _
Tip As String)
With nfIconData
.hwnd = MeHwnd
.uID = MeIcon
.uFlags = NIF_ICON Or NIF_MESSAGE Or NIF_TIP
.uCallbackMessage = WM_RBUTTONUP
.hIcon = MeIconHandle
.szTip = Tip & Chr$(0)
.cbSize = Len(nfIconData)
End With
Shell_NotifyIcon NIM_ADD, nfIconData
End Sub

Function FindWindowd(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
FindWindowd = FindWindow(lpClassName, lpWindowName)
End Function

Function ExtractIcond(ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal _
nIconIndex As Long) As Long
ExtractIcond = ExtractIcon(hInst, lpszExeFileName, nIconIndex)
End Function

Sub ShowUserForm()
Application.Visible = False
UserForm1.Show 1
End Sub
'******************************************************'
'****************** END MODULE CODE *******************'
'******************************************************'

To try this out, simply run the ShowUserForm subroutine. Pretty cool, huh? Of course this can be quickly ported to run in other office programs as well as vb6, but since I love Excel I see no reason to generalize it for the sake of this post :)

Wednesday, May 14, 2008

Splitting a worksheet based on a column's value

One question I come across a lot is when someone has a lot of data on a single worksheet, and wants to split it out to multiple worksheets based on the value of a column. For example, if someone had the following birthday data on a worksheet, and wanted to have a sheet for each month. Assume this sample data:

SHEET NAME: Birthdays
Name Month Day
Emmy August 27
Bill July 3
Andy November 24
Bob July 27
Dana June 24


And after running a macro:
SHEET NAME: August
Name Month Day
Emmy August 27

SHEET NAME: July
Name Month Day
Bill July 3
Bob July 27

SHEET NAME: November
Name Month Day
Andy November 24

SHEET NAME: June
Name Month Day
Dana June 24


That is a simplistic example, but I see something like that frequently. The following code will do that for you, just set the column to use for the splitting-data and run.

Option Explicit
Sub SplitIntoMultipleSheetsBasedOnColumn()
Dim TheColumn As Range, ValRG As Range
Dim UniqVals() As Variant, AllVals() As Variant
Dim FirstDataRow As Long, i As Long, Cnt As Long

'Unique values in the column specified by TheColumn are given their own worksheet,
' and their entire row is copied to that worksheet
Set TheColumn = Columns("G") 'must be a single column
FirstDataRow = 2 'so that the header row(s) aren't turned into a sheet

Set ValRG = Intersect(TheColumn, TheColumn.Worksheet.UsedRange, _
TheColumn.Worksheet.Rows(FirstDataRow & ":" & TheColumn.Worksheet.Rows.Count))
If ValRG Is Nothing Then
MsgBox "No data found. Exiting."
Exit Sub
End If
ReDim UniqVals(0)
Cnt = 0
AllVals = ValRG.Value
For i = 1 To UBound(AllVals, 1)
If Not InArray(UniqVals, AllVals(i, 1)) Then
ReDim Preserve UniqVals(Cnt)
UniqVals(Cnt) = AllVals(i, 1)
Cnt = Cnt + 1
End If
Next 'i

Application.ScreenUpdating = False
For i = LBound(UniqVals) To UBound(UniqVals)
Set ValRG = FoundRange(TheColumn, UniqVals(i))
With Sheets.Add(After:=Sheets(Sheets.Count))
On Error Resume Next
.Name = ValidSheetName(UniqVals(i))
On Error GoTo 0
If FirstDataRow > 1 Then TheColumn.Worksheet.Range(TheColumn.Cells(1), _
TheColumn.Cells(FirstDataRow - 1)).EntireRow.Copy .Range("A1")
ValRG.EntireRow.Copy .Range("A" & FirstDataRow)
End With
Next 'i
Application.ScreenUpdating = True
End Sub
Private Function ValidSheetName(ByVal DesiredSheetName As String) As String
On Error Resume Next
ValidSheetName = Left(Replace(Replace(Replace(Replace(Replace(Replace(Replace( _
DesiredSheetName, ":", ""), "\", ""), "/", ""), "?", ""), "*", ""), "[", ""), _
"]", ""), 31)
End Function
Public Function InArray(ByRef vArray(), ByVal vValue) As Boolean
Dim i As Long
For i = LBound(vArray) To UBound(vArray)
If vArray(i) = vValue Then
InArray = True
Exit Function
End If
Next 'i
InArray = False
End Function
Function FoundRange(ByVal vRG As Range, ByVal vVal) As Range
Dim FND As Range, FND1 As Range
Set FND = vRG.Find(vVal, LookIn:=xlValues, LookAt:=xlWhole)
If Not FND Is Nothing Then
Set FoundRange = FND
Set FND1 = FND
Set FND = vRG.FindNext(FND)
Do Until FND.Address = FND1.Address
Set FoundRange = Union(FoundRange, FND)
Set FND = vRG.FindNext(FND)
Loop
End If
End Function


Note that you don't need to sort your sheet first, though it will make things run a bit smoother. I originally had the code go through an array containing the sheet data, but a lot of the format was being lost. This way takes slightly longer but everything stays to the new sheets. I use this for my job, and I'm sure other people can use it for theirs as well.

Tuesday, May 13, 2008

VBA will be back for Mac Office!

Ok, those of you that know me know that I'm not a huge Mac/Apple fan. I'm not really much against them, though I don't like the 'anti-pc' tone of their commercials versus a traditional 'pro-mac' commercial. Regardless, being a huge Excel and VBA fan, I felt bad for the many mac users that bought Office 2008 only to find out VBA was gone. Now I know the reasons behind it, so I'm not going to whinge about it, but it still sucked. That move alone would have prompted me to leave my mac in the dust (if I had one), despite it being a decision on Microsoft's part rather than Apple's.

So I was very happy to read today that VBA will be back in the next version of Mac Office! The press release is really about the release of SP1, but said MS will be bringing VBA back at a yet-unspecified future date. Score one for the good guys!

Go VBA!

Monday, May 12, 2008

New Computer

Well, I got a new computer a couple weeks ago and have been slowly integrating it into my normal usage. It didn't help that the parts came over the course of a week (most in the first 2 days, save some necessary sata cables that rendered the rest of it useless), and that my pretty new monitor (hp w2207) worked well with my old pc, delaying my motivation/installation. For those that are curious/bored, heres the specs on my new computer:

  • CPU: Intel E8400 -- 3.0ghz core 2 duo, fast goodness
  • Motherboard: ASUS nForce 750i -- Does everything I need and much more (everything I may need someday?)
  • RAM: G.Skill 4GB -- Nothing too special here, 4gb of DDR2 800 PC2 6400. Works very well though and no issues installing it at all
  • Video Card: eVGA GeForce 8800GT -- Sweeeeeeeeeet card. I'm immensely impressed at what this thing can do. PCIx 2.0x16, 512mb, 256bit, GDDR3, HDCP, SLI; this should keep my graphics near-high-end for years
  • Monitor: HP w2207 -- Great monitor. Get one. Actually, why not get two? 22" widescreen LCD, 1680x1050, 1000:1, DVI or VGA input, even has USB ports and can be easily rotated to be in Portrait mode instead of the usual landscape mode (VERY useful for long spreadsheets and web pages)
  • Hard Drive: WD Caviar 500gb sata 7200/16mb --Just a big hard drive.. I had read a bunch of good reviews, so I got one. Actually I bought this last Friday and should get it tomorrow hopefully, currently have an old 120gb drive in the machine right now (which will stay, my OS is on it)
  • DVD/CD drive: Samsung DVD burner -- Just a fast, robust, sata dvd burner. Lots of good features on it, though I've only used it so far for reading cds/dvds and haven't burned anything yet. Soon enough, I'm sure, as I can't wait to try the dual layer. Hoping the lightscribe isn't just marketing hype
  • Case/PSU: CoolerMaster RC-690-KKA1-GP -- Big case (bigger than I was expecting :/), lots of great tool-less features and lots of room for fans. Included a few fans to begin with, as well as a 550w PSU, plus external ports. And it is black. I guess the only thing I don't like is the neon blue glowing in the front, but certainly not enough to want a new one. Way too many positives outweight it.
So yeah, that is it. Installed XP x64 on it, Firefox, Visual Studio 2008, Office 2007 ultimate (woo!), and Guild Wars. Haven't tried that other than just a quick check to see how it looked, which was much better than my old pc. The release of Mario Kart Wii kinda curbed my enthusiasm for the new computer, it'll return soon enough I'm sure.

Oh, and I got a USB wi-fi adapter for it too, in case I decide not to anchor it near my router. Much better than the one I had anyways for my laptop, so it was money well spent.

Overall, the computer was a little more than I had planned to spend, but if nothing else I can thank the $600 economic stimulus refund I got, and simply write the monitor off as a personal 30th birthday present.