29 August 2012

A Fix for TWebBrowser's Irritating Click Sound

For a a long time now, my CodeSnip program's propensity to make little clicking noises when some pages are displayed or some internal links are clicked has been irritating me to death.

Of course, this is because CodeSnip uses Delphi's TWebBrowser control to display the main part of its UI: and TWebBrowser wraps the Internet Explorer display engine, and that's where the well known clicks come from.

Many people say that the click is part of the expected user experience, but while I agree for apps that present a noticeably web inspired interface, I don't think it's helpful in apps like CodeSnip that simply use the browser control as a way of diplaying a richly formatted user interface: users aren't supposed to even know there's a browser control there.

Any road up, I've finally found how to turn off the dreaded click using a proper API rather than a kludge that hacks about the registry. I've seen this solution discussed for VB and C#, but not for Delphi, so here's the solution I'm using.

It uses the CoInternetSetFeatureEnabled function exported by UrlMon.dll from Windows XP SP2 / Windows Server 2003 and Internet Explorer v6.0.

My app needs to also support Win 2K, so I can't import the function statically - we need a dynamic approach. This is what I came up with:

First declare a type for the imported function and a global variable to reference the function:

type
  TCoInternetSetFeatureEnabled = function(
    FeatureEntry: DWORD;
    Flags: DWORD;
    Enable: BOOL
  ): HResult; stdcall;
 
var
  CoInternetSetFeatureEnabled: TCoInternetSetFeatureEnabled;

Now declare a function to use in case we can't import CoInternetSetFeatureEnabled from UrlMon.dll:

function CoInternetSetFeatureEnabledFallback(FeatureEntry: DWORD;
  Flags: DWORD; Enable: BOOL): HResult;  stdcall;
begin
  Result := E_NOTIMPL;
end;

This function does nothing except return E_NOTIMPL to show that it is not implemented.

Next comes a procedure that is to be called from the unit's initialization section to load CoInternetSetFeatureEnabled if possible and if not to use CoInternetSetFeatureEnabledFallback:

procedure Init;
begin
  LibHandle := SafeLoadLibrary('urlmon.dll');
  CoInternetSetFeatureEnabled := nil;
  if LibHandle <> 0 then
    CoInternetSetFeatureEnabled := GetProcAddress(
      LibHandle, 'CoInternetSetFeatureEnabled'
    );
  if not Assigned(CoInternetSetFeatureEnabled) then
    CoInternetSetFeatureEnabled := CoInternetSetFeatureEnabledFallback;
end;

At the end of the unit we need the following initialisation and finalisation code:

initialization
 
Init;
 
finalization
 
FreeLibrary(LibHandle);
 
end.

Now we have an implementation of CoInternetSetFeatureEnabled that we can call safely, we can switch off those horrid little clicks by calling the routine with the necessary parameters:

procedure SwitchOffBrowserSounds;
const
  FEATURE_DISABLE_NAVIGATION_SOUNDS       = 21;
  SET_FEATURE_ON_PROCESS                  = $00000002;
begin
  CoInternetSetFeatureEnabled(
    FEATURE_DISABLE_NAVIGATION_SOUNDS, SET_FEATURE_ON_PROCESS, True
  );
end;

The FEATURE_DISABLE_NAVIGATION_SOUNDS is the important value here: it switches off the IE ticks and is one of a number of "feature controls" that can be passed to CoInternetSetFeatureEnabled. You can see the full list at http://msdn.microsoft.com/en-us/library/ms537169.

SET_FEATURE_ON_PROCESS makes the change only for the current process so we don't interfere with the settings that apply in other applications. Again, there are flags for different options: see http://msdn.microsoft.com/en-us/library/ms537168.

Of course this code has no effect if UrlMon.dll does not export CoInternetSetFeatureEnabled, but at least it fails gracefully.

At last peace reigns over the CodeSnip UI! (Or rather, it will when v4.0 beta 3 is available).