|
|||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionAnyone who has done any Windows Forms programming in the .NET Framework is familiar with the BackgroundThe Win32 API includes three functions for presenting message boxes to the user. These are Getting access to the Design NotesThe
In my opinion, the I adopted the object-oriented model I suggested above for the The Demo ProjectThe included solution, MessageBoxIndirect.sln, contains a Windows Application where the I have included a Visual C++ solution called Win32Resources which builds a resource DLL containing a custom icon. If you would like to try the part of the demo that loads a custom icon from a separate DLL, build this solution and copy the output DLL to the same location as the demo project executable (\MessageBoxIndirect\bin\Debug or \MessageBoxIndirect\bin\Release). ModalitiesThere are three basic behaviors that a message box can have with respect to what else the user is allowed to do while the message box is up. These are application-modal, task-modal, and system-modal. Application-modal message boxes accept an owner window as a parameter and disallow any interaction with the given window until the message box is dismissed. Task-modal message boxes work the same way, except that the resulting message box window is topmost. This is intended to indicate a relatively serious situation. Finally, system-modal message boxes disallow any interaction with all top-level windows from the calling application without requiring you to pass an owner window. Here is an example of specifying the modality using the MessageBoxIndirect mb = new MessageBoxIndirect( this, "App Modal", "Test" );
mb.Modality = MessageBoxIndirect.MessageBoxExModality.AppModal;
DialogResult dr = mb.Show();
Passing a LangIDThe MessageBoxIndirect mb = new MessageBoxIndirect( "Pass a LangID: " +
Thread.CurrentThread.CurrentUICulture.LCID.ToString(), "Test" );
mb.LanguageID = (uint) Thread.CurrentThread.CurrentUICulture.LCID;
DialogResult dr = mb.Show();
Adding a Help ButtonYou can add a Help button to your message box by setting the MessageBoxIndirect mb = new MessageBoxIndirect( "Help Button",
"Test", MessageBoxButtons.YesNoCancel );
mb.ShowHelp = true;
mb.ContextHelpID = 555;
mb.Callback = new MessageBoxIndirect.MsgBoxCallback( this.ShowHelp );
DialogResult result = mb.Show();
The The second way to handle help is to request that a MessageBoxIndirect mb = new MessageBoxIndirect( this,
"Help Button", "Test", MessageBoxButtons.YesNoCancel );
mb.ShowHelp = true;
mb.ContextHelpID = 444;
DialogResult result = mb.Show();
To handle the Adding a Custom IconThis was certainly the most difficult and yet in my opinion the most interesting part of my effort to wrap the 1. You can use a resource-editing tool after building your assembly to slam your icons in as Win32 resources. Although .NET doesn't use the same format for resources, it does create standard Win32 binaries, and there's nothing preventing you from adding resources to them. I do not personally have a tool to recommend to pull this off, so I will not discuss this option further. 2. You can build using the csc.exe or vbc.exe command-line compilers, which support a /win32res flag that allows you to specify a compiled resource file (.res) to link in as Win32 resources. Of course, this option means you must abandon Visual Studio as your build environment, which can be a problem particularly in large projects. Also, the /win32res flag is incompatible with the /win32icon flag for specifying an application icon, so if you choose this route, you'll have to make sure your desired application icon is the lowest-numbered icon resource in your Win32 .res file. In the sample code, there is a build.bat script that demonstrates this technique using a .res file named Win32Resources.res that I supply. On the demo form, click the "Custom Icon (This Exe)" button to try it out. Note that this button will not give you a custom icon unless you compile using the command line and the /win32res flag. 3. You can dynamically load a resource DLL and pass the resulting instance handle to if( hWin32Resources == IntPtr.Zero )
{
hWin32Resources = LoadLibraryEx( Application.StartupPath +
"\\Win32Resources.dll", IntPtr.Zero, 0 );
Debug.Assert( hWin32Resources != IntPtr.Zero );
}
// Win32 Resource ID of the icon we want to put in the message box.
const int Smiley = 102;
MessageBoxIndirect mb = new MessageBoxIndirect( "Custom Icon", "Test" );
// Load the icon from the resource DLL that we loaded.
mb.Instance = hWin32Resources;
mb.UserIcon = new IntPtr(Smiley);
DialogResult result = mb.Show();
If this doesn't work for you, check to make sure that the Win32Resources DLL is in the correct location (next to the application executable). ImplementationA detailed discussion of the .NET The [DllImport("user32", EntryPoint="MessageBoxIndirect")]
private static extern int _MessageBoxIndirect(
ref MSGBOXPARAMS msgboxParams );
Note the "ref" decoration indicating that the underlying API accepts a pointer to a structure. The structure that gets passed to this function has the following managed declaration: [StructLayout(LayoutKind.Sequential)]
public struct MSGBOXPARAMS
{
public uint cbSize;
public IntPtr hwndOwner;
public IntPtr hInstance;
public String lpszText;
public String lpszCaption;
public uint dwStyle;
public IntPtr lpszIcon;
public IntPtr dwContextHelpId;
public MsgBoxCallback lpfnMsgBoxCallback;
public uint dwLanguageId;
};
This structure (along with the other declarations that follow) originate in the winuser.h Platform SDK header file. Note that we use This structure includes a member, public delegate void MsgBoxCallback( HELPINFO lpHelpInfo );
The [StructLayout(LayoutKind.Sequential)]
public struct HELPINFO
{
public uint cbSize;
public int iContextType;
public int iCtrlId;
public IntPtr hItemHandle;
public IntPtr dwContextId;
public POINT MousePos;
};
As discussed above, one way to handle the help button on the message box is to process the public static HELPINFO UnmarshalFrom( IntPtr lParam )
{
return (HELPINFO) Marshal.PtrToStructure(
lParam, typeof( HELPINFO ) );
}
Finally, we define a number of message-box-related constants from winuser.h to use in our implementation, along with the following enumeration to represent the various modal behaviors a message box can take: public enum MessageBoxExModality : uint
{
AppModal = MB_APPLMODAL,
SystemModal = MB_SYSTEMMODAL,
TaskModal = MB_TASKMODAL
}
Getting the custom system icon to work was a bit tricky. In theory, all one has to do is send a DialogResult retval = DialogResult.Cancel;
try
{
// Only hook if we have a reason to, namely, to set the custom icon.
if( _sysSmallIcon != IntPtr.Zero )
{
HookProc CbtHookProcedure = new HookProc(CbtHookProc);
hHook = SetWindowsHookEx(WH_CBT, CbtHookProcedure, (IntPtr) 0,
AppDomain.GetCurrentThreadId());
}
retval = (DialogResult) _MessageBoxIndirect( ref parms );
}
finally
{
if( hHook > 0 )
{
UnhookWindowsHookEx(hHook);
hHook = 0;
}
}
private int CbtHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if( nCode == HCBT_CREATEWND )
{
// Make sure this is really a dialog.
StringBuilder sb = new StringBuilder();
sb.Capacity = 100;
GetClassName( wParam, sb, sb.Capacity );
string className = sb.ToString();
if( className == "#32770" )
{
// Found it, look to set the icon if necessary.
if( _sysSmallIcon != IntPtr.Zero )
{
EnsureInstance();
IntPtr hSmallSysIcon = LoadIcon( Instance, _sysSmallIcon );
if( hSmallSysIcon != IntPtr.Zero )
{
SendMessage( wParam, WM_SETICON, new IntPtr(ICON_SMALL),
hSmallSysIcon );
}
}
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
When hooking, it is essential to end with a call to Most of the rest of the code in the History
|
||||||||||||||||||||||||||||||||||||||||