DrawText draws Segoe UI font texts incorrectly - winapi

I encountered one problem with drawing texts using the Windows API DrawText call for the Segoe UI font:
This image demonstrates the problem: the specified text is shifted a little bit to the right in the specified rectangle so the last character is clipped (the best example is the 0 digit).
Our drawing routine works well for other fonts, and the problem occurs only for Segoe UI.
What it could be and how to solve it?
Doing this in a VB6 OCX project on Windows 8 Pro 64-bit if it matters.
The corresponding code source snippet is the following:
' Only draws or measure text (if DT_CALCRECT is specified)
' using the native WinAPI flags:
Public Sub gpInternalDrawText( _
ByVal lHDC As Long, _
ByRef sText As String, _
ByRef tR As RECT, _
ByVal lFlags As Long _
)
' Allows Unicode rendering of text under NT/2000/XP
If (g_bIsNt) Then
' NT4 crashes with ptr = 0
If StrPtr(sText) <> 0 Then
DrawTextW lHDC, StrPtr(sText), -1, tR, lFlags
End If
Else
DrawTextA lHDC, sText, -1, tR, lFlags
End If
End Sub
' Draws the string in the specifed rectangle.
' Should not be called to calculate text size
' (with DT_CALCRECT flag - use gpInternalDrawText instead)
Public Sub DrawText( _
ByVal lHDC As Long, _
ByRef sText As String, _
ByRef rcText As RECT, _
ByVal lFlags As Long, _
Optional ByVal eAlignH As Long = 0, _
Optional ByVal eAlignV As Long = 0 _
)
' *** Automatically turns processing prefixes off (if required)
If (lFlags And &H200000) = 0 Then
lFlags = lFlags Or DT_NOPREFIX
Else
lFlags = lFlags Xor DT_PREFIXONLY
End If
' *** We can modify rcText below, so do it with its copy
Dim rcDrawText As RECT
LSet rcDrawText = rcText
' *** Getting the full set of API flags for our text
Select Case eAlignH
' in fact don't need that as DT_LEFT=0:
' Case igAlignHLeft
' lFlags = lFlags Or DT_LEFT
Case igAlignHCenter
lFlags = lFlags Or DT_CENTER
Case igAlignHRight
lFlags = lFlags Or DT_RIGHT
End Select
If (lFlags And DT_SINGLELINE) <> 0 Then
Select Case eAlignV
' in fact don't need that as DT_TOP=0:
' Case igAlignVTop
' lFlags = lFlags Or DT_TOP
Case igAlignVCenter
lFlags = lFlags Or DT_VCENTER
Case igAlignVBottom
lFlags = lFlags Or DT_BOTTOM
End Select
Else
If eAlignV <> igAlignVTop Then
Dim rcCalcRect As RECT
LSet rcCalcRect = rcText
gpInternalDrawText lHDC, sText, rcCalcRect, lFlags Or DT_CALCRECT
Dim lTextHeight As Long
lTextHeight = rcCalcRect.Bottom - rcCalcRect.Top
Select Case eAlignV
Case igAlignVCenter
' simplified (rcText.Top + rcText.Bottom) / 2 - lTextHeight / 2
' should be integer division because of rounding erros in the case of "/"
rcDrawText.Top = (rcDrawText.Top + rcDrawText.Bottom - lTextHeight) \ 2
Case igAlignVBottom
rcDrawText.Top = rcDrawText.Bottom - lTextHeight
End Select
End If
End If
' *** Finally draw the text
Const FIXED_PATH_ELLIPSIS_FLAGS As Long = DT_SINGLELINE Or DT_PATH_ELLIPSIS
If (lFlags And FIXED_PATH_ELLIPSIS_FLAGS) = FIXED_PATH_ELLIPSIS_FLAGS Then
DrawText_FixedPathEllipsis lHDC, sText, rcDrawText, lFlags
Else
gpInternalDrawText lHDC, sText, rcDrawText, lFlags
End If
End Sub
The font for the UserControl DC is set using this code:
Public Function FontHandle(fnt As IFont) As Long
FontHandle = fnt.hFont
End Function
Private Sub pApplyFont()
If (m_hFntDC <> 0) Then
If (m_hDC <> 0) Then
If (m_hFntOldDC <> 0) Then
SelectObject m_hDC, m_hFntOldDC
End If
End If
End If
m_hFntDC = FontHandle(UserControl.Font)
If (m_hDC <> 0) Then
m_hFntOldDC = SelectObject(m_hDC, m_hFntDC)
End If
End Sub
, where
m_hDC = CreateCompatibleDC(UserControl.hdc)

The problem is the output quality that you are using. You are using ANTIALIASED_QUALITY. Segoe UI has been designed for clear type. It looks great with clear type, but terrible with standard anti-aliasing. Switch to clear type (set lqQuality to CLEARTYPE_QUALITY) and you will get much better results.
This image demonstrates rendering of 10pt Segoe UI with the two quality options discussed above.

Yes, David Heffernan was right - I needed to turn the ClearType setting for the whole OS on:

Related

Visual Basic 6 - Argument not optional

I have this very simple code:
Private Sub Image87_Click()
PrintRTFWithMargins
End Sub
PrintRTFWithMargins is a function, which should "hopefully" print the contents of a RichTextBox. Every time I do run the code though, it gives me "Argument not optional" on PrintRTFWithMargins.
The code inside the function has already Option Explicit at the start, and I've tried to put it at the start of the Image87_Click too, but nothing.
Here's the code of PrintRTFWithMargins:
Option Explicit
Private Type Rect
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type CHARRANGE
cpMin As Long
cpMax As Long
End Type
Private Type FORMATRANGE
hdc As Long
hdcTarget As Long
rc As Rect
rcPage As Rect
chrg As CHARRANGE
End Type
Private Const WM_USER As Long = &H400
Private Const EM_FORMATRANGE As Long = WM_USER + 57
Private Const EM_SETTARGETDEVICE As Long = WM_USER + 72
Private Const PHYSICALOFFSETX As Long = 112
Private Const PHYSICALOFFSETY As Long = 113
Private Declare Function GetDeviceCaps Lib "gdi32" _
(ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function SendMessage Lib "USER32" _
Alias "SendMessageA" (ByVal hWnd As Long, ByVal msg As Long, _
ByVal wp As Long, lp As Any) As Long
Public Function PrintRTFWithMargins(RTFControl As Object, _
ByVal LeftMargin As Single, ByVal TopMargin As Single, _
ByVal RightMargin As Single, ByVal BottomMargin As Single) _
As Boolean
'********************************************************8
'PURPOSE: Prints Contents of RTF Control with Margins
'PARAMETERS:
'RTFControl: RichTextBox Control For Printing
'LeftMargin: Left Margin in Inches
'TopMargin: TopMargin in Inches
'RightMargin: RightMargin in Inches
'BottomMargin: BottomMargin in Inches
'***************************************************************
On Error GoTo ErrorHandler
'*************************************************************
'I DO THIS BECAUSE IT IS MY UNDERSTANDING THAT
'WHEN CALLING A SERVER DLL, YOU CAN RUN INTO
'PROBLEMS WHEN USING EARLY BINDING WHEN A PARAMETER
'IS A CONTROL OR A CUSTOM OBJECT. IF YOU JUST PLUG THIS INTO
'A FORM, YOU CAN DECLARE RTFCONTROL AS RICHTEXTBOX
'AND COMMENT OUT THE FOLLOWING LINE
If Not TypeOf RTFControl Is RichTextBox Then Exit Function
'**************************************************************
Dim lngLeftOffset As Long
Dim lngTopOffSet As Long
Dim lngLeftMargin As Long
Dim lngTopMargin As Long
Dim lngRightMargin As Long
Dim lngBottomMargin As Long
Dim typFr As FORMATRANGE
Dim rectPrintTarget As Rect
Dim rectPage As Rect
Dim lngTxtLen As Long
Dim lngPos As Long
Dim lngRet As Long
Dim iTempScaleMode As Integer
iTempScaleMode = Printer.ScaleMode
' needed to get a Printer.hDC
Printer.Print ""
Printer.ScaleMode = vbTwips
' Get the offsets to printable area in twips
lngLeftOffset = Printer.ScaleX(GetDeviceCaps(Printer.hdc, _
PHYSICALOFFSETX), vbPixels, vbTwips)
lngTopOffSet = Printer.ScaleY(GetDeviceCaps(Printer.hdc, _
PHYSICALOFFSETY), vbPixels, vbTwips)
' Get Margins in Twips
lngLeftMargin = InchesToTwips(LeftMargin) - lngLeftOffset
lngTopMargin = InchesToTwips(TopMargin) - lngTopOffSet
lngRightMargin = (Printer.Width - _
InchesToTwips(RightMargin)) - lngLeftOffset
lngBottomMargin = (Printer.Height - _
InchesToTwips(BottomMargin)) - lngTopOffSet
' Set printable area rect
rectPage.Left = 0
rectPage.Top = 0
rectPage.Right = Printer.ScaleWidth
rectPage.Bottom = Printer.ScaleHeight
' Set rect in which to print, based on margins passed in
rectPrintTarget.Left = lngLeftMargin
rectPrintTarget.Top = lngTopMargin
rectPrintTarget.Right = lngRightMargin
rectPrintTarget.Bottom = lngBottomMargin
' Set up the printer for this print job
typFr.hdc = Printer.hdc 'for rendering
typFr.hdcTarget = Printer.hdc 'for formatting
typFr.rc = rectPrintTarget
typFr.rcPage = rectPage
typFr.chrg.cpMin = 0
typFr.chrg.cpMax = -1
' Get length of text in the RichTextBox Control
lngTxtLen = Len(Form1.RichTextBox1.Text)
' print page by page
Do
' Print the page by sending EM_FORMATRANGE message
'Allows you to range of text within a specific device
'here, the device is the printer, which must be specified
'as hdc and hdcTarget of the FORMATRANGE structure
lngPos = SendMessage(Form1.RichTextBox1.hWnd, EM_FORMATRANGE, _
True, typFr)
If lngPos >= lngTxtLen Then Exit Do 'Done
typFr.chrg.cpMin = lngPos ' Starting position next page
Printer.NewPage ' go to next page
Printer.Print "" 'to get hDC again
typFr.hdc = Printer.hdc
typFr.hdcTarget = Printer.hdc
Loop
' Done
Printer.EndDoc
' This frees memory
lngRet = SendMessage(Form1.RichTextBox1.hWnd, EM_FORMATRANGE, _
False, Null)
Printer.ScaleMode = iTempScaleMode
PrintRTFWithMargins = True
Exit Function
ErrorHandler:
Err.Raise Err.Number, , Err.Description
End Function
Private Function InchesToTwips(ByVal Inches As Single) As Single
InchesToTwips = 1440 * Inches
End Function
I really, really don't know what else to put. It's such a simple code, just running a function, and yet "Argument not optional". It's single-hand the most annoying Visual Basic error I've ever experienced, because it's so dumb
'''
Call your function as:
Dim retVal as Boolean
retVal = PrintRTFWithMargins(RichTextBox1, 1.1, 1, 1, 1)

Win32 tooltip gray line bug in Windows 10

We have been using code that creates classical Win32 multiline tooltips in our legacy VB6 component for many years, since the times of Windows XP. It works fine in all latest versions of MS Windows (7, 8.1) except Windows 10. A parasitic horizontal gray line appears in the tooltip in this OS. The best demonstration of this problem is a tooltip window containing several lines of text (the main tip text is multiline and/or the tooltip has a bold title):
The correct tooltip should look like this (a screen from Windows 8.1):
Below is one more example of the same problem when the tooltip window does not have tile/icon but contains only multiline text:
This parasitic gray line is also present in a single-line tooltip - though it is not noticeable at first look:
What it could be? Is it a bug in Windows 10, or something has changed in the tooltip API?
Below is the code of the method used to initialize a tooltip:
Public Function Create(ByVal ParentHwnd As Long) As Boolean
Dim lWinStyle As Long
If m_lTTHwnd <> 0 Then
DestroyWindow m_lTTHwnd
End If
m_lParentHwnd = ParentHwnd
lWinStyle = TTS_ALWAYSTIP Or TTS_NOPREFIX
m_lTTHwnd = CreateWindowExA(0&, _
TOOLTIPS_CLASS, _
vbNullString, _
lWinStyle, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
0&, _
0&, _
App.hInstance, _
0&)
'now set our tooltip info structure
Dim tiA As TOOLINFOA
Dim tiW As TOOLINFOW
If g_bIsNt Then
With tiW
.lSize = Len(tiW)
.lFlags = TTF_SUBCLASS Or TTF_IDISHWND
.hWnd = m_lParentHwnd
.lId = m_lParentHwnd '0
.hInstance = App.hInstance
.lpStr = StrPtr(mvarTipText)
End With
Else
With tiA
.lSize = Len(tiA)
.lFlags = TTF_SUBCLASS Or TTF_IDISHWND
.hWnd = m_lParentHwnd
.lId = m_lParentHwnd
.hInstance = App.hInstance
.lpStr = mvarTipText
End With
End If
'add the tooltip structure
If g_bIsNt Then
SendMessage m_lTTHwnd, TTM_ADDTOOLW, 0&, tiW
Else
SendMessage m_lTTHwnd, TTM_ADDTOOLA, 0&, tiA
End If
'if we want a title or we want an icon
If mvarTitle <> vbNullString Or mvarIcon <> igToolTipIconNone Then
If g_bIsNt Then
SendMessage m_lTTHwnd, TTM_SETTITLEW, mvarIcon, ByVal StrPtr(mvarTitle)
Else
SendMessage m_lTTHwnd, TTM_SETTITLEA, mvarIcon, ByVal mvarTitle
End If
End If
' set the time parameters
SendMessageByLongA m_lTTHwnd, TTM_SETDELAYTIME, TTDT_AUTOPOP, mvarVisibleTime
SendMessageByLongA m_lTTHwnd, TTM_SETDELAYTIME, TTDT_INITIAL, mvarDelayTime
'according to MSDN, we should set TTM_SETMAXTIPWIDTH to a positive value
'to enable multiline tooltips
SendMessageByLongA m_lTTHwnd, TTM_SETMAXTIPWIDTH, 0, 100000
End Function
To solve the problem, we should not set the hwnd field of the TOOLINFO structure. The corresponding part of the code should look like this:
'now set our tooltip info structure
Dim tiA As TOOLINFOA
Dim tiW As TOOLINFOW
If g_bIsNt Then
With tiW
.lSize = Len(tiW)
.lFlags = TTF_SUBCLASS Or TTF_IDISHWND
.lId = m_lParentHwnd
.hInstance = App.hInstance
.lpStr = StrPtr(mvarTipText)
End With
Else
With tiA
.lSize = Len(tiA)
.lFlags = TTF_SUBCLASS Or TTF_IDISHWND
.lId = m_lParentHwnd
.hInstance = App.hInstance
.lpStr = mvarTipText
End With
End If

A Color Picker Control for Visual Basic 6 (VB6)

I am looking for a free color picker control for Visual Basic 6. Something like this or this. Is there any?
Update. Here is what I found so far:
A Photoshop-style Color Picker
Color Picker Control with Sample Code
Have a look at: https://web.archive.org/web/20111001144343/http://www.devx.com/vb2themax/Tip/19257
It's supposed to show the standard color dialog (ChooseColor API in comdlg32.dll).
For convenience, here's the code:
Private Type ChooseColorStruct
lStructSize As Long
hwndOwner As Long
hInstance As Long
rgbResult As Long
lpCustColors As Long
flags As Long
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type
Private Declare Function ChooseColor Lib "comdlg32.dll" Alias "ChooseColorA" _
(lpChoosecolor As ChooseColorStruct) As Long
Private Declare Function OleTranslateColor Lib "oleaut32.dll" (ByVal lOleColor _
As Long, ByVal lHPalette As Long, lColorRef As Long) As Long
Private Const CC_RGBINIT = &H1&
Private Const CC_FULLOPEN = &H2&
Private Const CC_PREVENTFULLOPEN = &H4&
Private Const CC_SHOWHELP = &H8&
Private Const CC_ENABLEHOOK = &H10&
Private Const CC_ENABLETEMPLATE = &H20&
Private Const CC_ENABLETEMPLATEHANDLE = &H40&
Private Const CC_SOLIDCOLOR = &H80&
Private Const CC_ANYCOLOR = &H100&
Private Const CLR_INVALID = &HFFFF
' Show the common dialog for choosing a color.
' Return the chosen color, or -1 if the dialog is canceled
'
' hParent is the handle of the parent form
' bFullOpen specifies whether the dialog will be open with the Full style
' (allows to choose many more colors)
' InitColor is the color initially selected when the dialog is open
' Example:
' Dim oleNewColor As OLE_COLOR
' oleNewColor = ShowColorsDialog(Me.hwnd, True, vbRed)
' If oleNewColor <> -1 Then Me.BackColor = oleNewColor
Function ShowColorDialog(Optional ByVal hParent As Long, _
Optional ByVal bFullOpen As Boolean, Optional ByVal InitColor As OLE_COLOR) _
As Long
Dim CC As ChooseColorStruct
Dim aColorRef(15) As Long
Dim lInitColor As Long
' translate the initial OLE color to a long value
If InitColor <> 0 Then
If OleTranslateColor(InitColor, 0, lInitColor) Then
lInitColor = CLR_INVALID
End If
End If
'fill the ChooseColorStruct struct
With CC
.lStructSize = Len(CC)
.hwndOwner = hParent
.lpCustColors = VarPtr(aColorRef(0))
.rgbResult = lInitColor
.flags = CC_SOLIDCOLOR Or CC_ANYCOLOR Or CC_RGBINIT Or IIf(bFullOpen, _
CC_FULLOPEN, 0)
End With
' Show the dialog
If ChooseColor(CC) Then
'if not canceled, return the color
ShowColorDialog = CC.rgbResult
Else
'else return -1
ShowColorDialog = -1
End If
End Function
There is a color-picker built into VB6. The common dialog control can be used as a color picker.
Here's the code example from the VB6 manual
Private Sub Command1_Click()
' Set Cancel to True
CommonDialog1.CancelError = True
On Error GoTo ErrHandler
'Set the Flags property
CommonDialog1.Flags = cdlCCRGBInit
' Display the Color Dialog box
CommonDialog1.ShowColor
' Set the form's background color to selected color
Form1.BackColor = CommonDialog1.Color
Exit Sub
ErrHandler:
' User pressed the Cancel button
End Sub

Printer Page Size Problem

I am trying to set a custom paper size by doing:
Printer.Height = 2160
Printer.Width = 11900
But it doesn't seen to have any effect. After setting this up, i ask for that values and it returns the default ones. And this:
Printer.PaperSize = 256
Returns an error...
Any ideas??
Either your printer doesn't allow these properties to be set, or you're exceeding their maximum allowed values. From the Visual Basic Reference
If you set the Height and Width
properties for a printer driver that
doesn't allow these properties to be
set, no error occurs and the size of
the paper remains as it was. If you
set Height and Width for a printer
driver that allows only certain values
to be specified, no error occurs and
the property is set to whatever the
driver allows. For example, you could
set Height to 150 and the driver would
set it to 144.
I don't know why you're getting an error when you set the Papersize property to 256. It works for me. Also, the documentation states, "Setting a printer's Height or Width property automatically sets PaperSize to vbPRPSUser.", which equals 256.
I was actually involved with the same problem but I just happen to find a breakthrough.
First you need to create a custom form that defines you custom paper size. Then, you need to
refer to Windows API to check the form name you've just created. You'll get the for name
from an array returned from a function and use the array index where the form name was found.
Finally use it as the value for printer.papersize
Example below:
Public Type PRINTER_DEFAULTS
pDatatype As Long
pDevMode As Long
DesiredAccess As Long
End Type
Public Type FORM_INFO_1
Flags As Long
pName As Long ' String
Size As SIZEL
ImageableArea As RECTL
End Type
Public Declare Function EnumForms Lib "winspool.drv" Alias "EnumFormsA" _
(ByVal hPrinter As Long, ByVal Level As Long, ByRef pForm As Any, _
ByVal cbBuf As Long, ByRef pcbNeeded As Long, _
ByRef pcReturned As Long) As Long
Public Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal cbLength As Long)
Public Declare Sub Sleep Lib "KERNEL32" (ByVal dwMilliseconds As Long)
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
"OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
pDefault As PRINTER_DEFAULTS) As Long
Public Declare Function ClosePrinter Lib "winspool.drv" _
(ByVal hPrinter As Long) As Long
Public Declare Function lstrcpy Lib "KERNEL32" Alias "lstrcpyA" _
(ByVal lpString1 As String, ByRef lpString2 As Long) As Long
'UDF
Public Function PtrCtoVbString(ByVal Add As Long) As String
Dim sTemp As String * 512, x As Long
x = lstrcpy(sTemp, ByVal Add)
If (InStr(1, sTemp, Chr(0)) = 0) Then
PtrCtoVbString = ""
Else
PtrCtoVbString = Left(sTemp, InStr(1, sTemp, Chr(0)) - 1)
End If
End Function
Public Function IsFormExist(ByVal DeviceName As String, ByVal isFormName As String, ByVal PrinterHandle As Long) As Long
Dim NumForms As Long, i As Long
Dim FI1 As FORM_INFO_1
Dim pd As PRINTER_DEFAULTS
Dim aFI1() As FORM_INFO_1 ' Working FI1 array
Dim Temp() As Byte ' Temp FI1 array
Dim FormIndex As Integer
Dim BytesNeeded As Long
Dim RetVal As Long
On Error GoTo cleanup
FormIndex = 0
ReDim aFI1(1)
' First call retrieves the BytesNeeded.
RetVal = OpenPrinter(DeviceName, PrinterHandle, pd)
If (RetVal = 0) Or (PrinterHandle = 0) Then
'Can't access current printer. Bail out doing nothing
Exit Function
End If
RetVal = EnumForms(PrinterHandle, 1, aFI1(0), 0&, BytesNeeded, NumForms)
ReDim Temp(BytesNeeded)
ReDim aFI1(BytesNeeded / Len(FI1))
' Second call actually enumerates the supported forms.
RetVal = EnumForms(PrinterHandle, 1, Temp(0), BytesNeeded, BytesNeeded, _
NumForms)
Call CopyMemory(aFI1(0), Temp(0), BytesNeeded)
For i = 0 To NumForms - 1
With aFI1(i)
If isFormName = PtrCtoVbString(.pName) Then
' Found the desired form
FormIndex = i + 1
Exit For
End If
End With
Next i
IsFormExist = FormIndex ' Returns the number when form is found.
cleanup:
'Release the printer handle
If (PrinterHandle <> 0) Then Call ClosePrinter(PrinterHandle)
End Function
'Here We Go
dim papercode as long, printername as string, formname as string
printername=printer.Devicename
formname = "myform"
papercode=IsFormExist(printername, formname, Printer.hdc)
if papercode<>0 then
printer.papersize=papercode
end if
Give it a try, good luck
Are you sure the error isn't related to the maximum print width of the printer itself? Many printers have a max print width of 8.25" (11880) to allow 1/4" margins on either side of a 8.5" wide paper.
Quickest way to check would be to simply set the print wide to 11880 or lower and see if it works.
Another possibility would be permissions to the printer. If it's a shared network resource it may be locked down.
The solution is to use windows 98. It does not work with win2k, neither winXP. The same code, the same printer.
Regards.
I'm testing this code, but I can not see the custom form I created using printers and scanners in the Control Panel Windows XP Professional SP3.
Note: I could check in regedit that this form exists and its ID is 512 in a string value and it contains the name of the form created in the printers control panel.
Why this function does not return my custom form, I am using an HP Laserjet 1020.

Elevated Credentials for VB6

I need to get elevated credentials (to start a service) in a VB6 application, but only if the user needs to restart the service (I.e. I don't want to get elevated credentials whenever the application is started, only when the user selects restart). How can I do this in VB6?
Fairly easy, but the preferred way involves a new elevated process. This example uses itself run with a switch to know to perform the Service Start instead of normal operations:
VERSION 5.00
Begin VB.Form Form1
BorderStyle = 1 'Fixed Single
Caption = "Form1"
ClientHeight = 3060
ClientLeft = 45
ClientTop = 345
ClientWidth = 4560
LinkTopic = "Form1"
MaxButton = 0 'False
MinButton = 0 'False
ScaleHeight = 3060
ScaleWidth = 4560
StartUpPosition = 3 'Windows Default
Begin VB.CommandButton Command1
Caption = "Start Service"
Height = 495
Left = 1448
TabIndex = 0
Top = 1283
Width = 1665
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Const BCM_SETSHIELD As Long = &H160C&
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function ShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" ( _
ByVal hWnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Private Sub Command1_Click()
ShellExecute hWnd, "runas", App.EXEName & ".exe", "-start", CurDir$(), vbNormalFocus
End Sub
Private Sub Form_Load()
If UCase$(Trim$(Command$())) = "-START" Then
Caption = "Starting Service"
Command1.Visible = False
'Service starting functionality goes here.
Else
Caption = "Service Starter"
'For Shield to work you must have a Common Controls v. 6
'manifest and call InitCommonControls before loading
'this form (i.e. preferably from Sub Main).
SendMessage Command1.hWnd, BCM_SETSHIELD, 0&, 1&
Command1.Visible = True
End If
End Sub
One solution is to use the COM elevation moniker http://msdn.microsoft.com/en-us/library/ms679687(VS.85).aspx.
This link should be useful if your target is VB6 http://www.vbforums.com/showthread.php?t=459643.
You'll need to call into the WinAPI - CoImpersonateClient, or LogonUser.
Just remember to lower your privileges afterwards, and be DARN careful what you do when elevated (e.g. don't do ANYthing with user input).
Another option, which I believe is preferable (if available), is to use a COM+ configured object. You can have the COM+ subsystem manage credentials, and just limit access to call the object as necessary. This has the benefit of creating and ACTUAL trust boundary between low-privileged code and high-privileged code.

Resources