Open bstordrup opened 2 months ago
The proposal is very attractive.
Yeah as I probably gave away, I am not very keen on this API. How does the displayHelpButton
allow developer to interact with it? What are the expected values for the strings, file paths to .chm? web links? anything to shell-execute? Can you point to the Winforms PR implementing this?
Could we not have some Help event instead that would get called when the button is clicked?
displayHelpButton
makes the MessageBox
Windows API put a Help button in the MessageBox. And it will then send a WM_HELP
message to the owner [1], in the MB_HELP
description.
This means that the developer need listen for the WM_HELP
message in his own application and activate the relevant help items.
In WinForms repository, this is implemented using an overload of ShowCore
overloads that takes a HelpInfo
instance as parameter:
private static DialogResult ShowCore(
IWin32Window? owner,
string? text,
string? caption,
MessageBoxButtons buttons,
MessageBoxIcon icon,
MessageBoxDefaultButton defaultButton,
MessageBoxOptions options,
HelpInfo hpi)
{
DialogResult result = DialogResult.None;
try
{
PushHelpInfo(hpi);
result = ShowCore(owner, text, caption, buttons, icon, defaultButton, options, true);
}
finally
{
PopHelpInfo();
}
return result;
}
The other ShowCore
surrounds the call to the Windows API MessageBox
API call with a modal message loop:
Application.BeginModalMessageLoop();
try
{
return (DialogResult)PInvoke.MessageBox(handle.Handle, text, caption, style);
}
finally
{
Application.EndModalMessageLoop();
// Right after the dialog box is closed, Windows sends WM_SETFOCUS back to the previously active control
// but since we have disabled this thread main window the message is lost. So we have to send it again after
// we enable the main window.
PInvoke.SendMessage(handle, PInvoke.WM_SETFOCUS);
}
The methods PushHelpInfo
and PopHelpInfo
maintains the contents of a thread static HelpInfo[]?
array, and MessageBox
class exposes a static HelpInfo? HelpInfo
read only property that will return the first element in the HelpInfo[]?
array or null if the array is null or does not have any items.
This HelpInfo instance can then be retrieved in the callback for WM_HELP
message and used to activate the help system.
So then Help button can be just another button or part of the dialog options, and Window can have a sort of Help event, where the developer can do whatever they want.
You cannot ask people to listen for WM messages for such a simple thing, you will need to abstract it with an event.
You cannot ask people to listen for WM messages for such a simple thing, you will need to abstract it with an event.
In WinForms repository, this is handled on the Control
class with this method:
/// <summary>
/// Handles the WM_HELP message
/// </summary>
private unsafe void WmHelp(ref Message m)
{
// If there's currently a message box open - grab the help info from it.
HelpInfo? hpi = MessageBox.HelpInfo;
if (hpi is not null)
{
switch (hpi.Option)
{
case HelpInfo.HelpFileOption:
Help.ShowHelp(this, hpi.HelpFilePath);
break;
case HelpInfo.HelpKeywordOption:
Help.ShowHelp(this, hpi.HelpFilePath, hpi.Keyword);
break;
case HelpInfo.HelpNavigatorOption:
Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator);
break;
case HelpInfo.HelpObjectOption:
Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator, hpi.Param);
break;
}
}
// Note: info.hItemHandle is the handle of the window that sent the help message.
HELPINFO* info = (HELPINFO*)(nint)m.LParamInternal;
HelpEventArgs hevent = new(info->MousePos);
OnHelpRequested(hevent);
if (!hevent.Handled)
{
DefWndProc(ref m);
}
}
It is being called from the WndProc
method on Control
class - which is the place where Windows Messages are handled:
case PInvoke.WM_HELP:
WmHelp(ref m);
break;
The code shows that the WM_HELP
will trigger the OnHelpRequested
event on the Control
class.
I think a similar thing can be done on the Window
class in Wpf by extending the WindowFilterMessage
and responding on the WM_HELP
message in a similar way.
It might also be that a HelpRequested
should be added as a RoutedEventHandler
in UIElement
class and then triggered on the uiElement in either HwndSource
or HwndTarget
classes somewhere. Would need to experiment a little with how the WM_HELP
message could be picked up.
It should be added on Window class because Window is the only allowed owner of a MessageBox. Also it could be used for help button on the window itself.
@miloush, is it the HelpRequested
event, you mean?
In WinForms, that event is located on Control
class, which allows for the individual controls to respond to the event and provide specific help for that control.
If the HelpRequested
is only available on Window
class, then the window need to figure out where the user was requesting the help from and actively delegate the request there.
In Win32/Winforms, every control has a hwnd, which is not the case in WPF. But the event provides mouse coordinates, so it could be hit-tested to an individual element/visual. It can also apparently occur for a menu item.
An alternative way is to use the ApplicationCommands.Help
command. I haven't really explored the idea very much, just thinking out loud. But I don't like WPF trying to execute things automatically somehow, we don't even navigate Hyperlinks...
OK. Good input - will try to experiment with it and see if I can get something working.
Background and motivation
Split from the API Proposal in #9613.
The MessageBox API in Windows has an ability to make a Help button available in the UI. This involves the MB_HELP value in the style for the MessageBox call.
Doing so, developers will be able to integrate help into messages being shown making the end users able to read more information about a message.
API Proposal
The proposal is to add help related parameters to
MessageBox.Show
method in the same way as in WinForms repository.It would be something like this:
The implementation in WinForms repository will be a source of inspiration for implementing this.
Also, the underlying Windows API has a value for a default button 4. It can be made available also like this:
but this will more be for consistency with the underlying API. The value is not being used in the implementation that calculates a default button number based on the
MessageBoxResult defaultResult
.If the
DEFAULT_BUTTON4
should be usefull, it would mean to make a new enum for this and changing all calls toMessageBox.Show
to allow the caller to specify a default button instead of a default result. This would look like this:API Usage
This will open the help file specified on the key work specified
Alternative Designs
No response
Risks
It must be ensured that a valid handle for sending the WM_HELP message is found (1). This will have to be a part of the ShowCore changes. The implementation in WinForms repository handles it by getting the Active Window if an owner is not specified.