If you're using a multi-monitor setup with Windows, you might have encountered issues where the taskbar on your secondary monitor fails to appear on Windows startup. Manually toggling the taskbar settings can become a tedious workaround:
The taskbar on the second monitor sometimes is not shown
even if the setting "Show taskbar on all displays" is switched on.
In this blog post, we'll explore how to automate this task using Delphi.
Understanding the Taskbar Settings
Windows allows users to configure taskbars on multiple displays through the Settings app. The relevant setting is stored in the Windows registry under the path HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced with the value name MMTaskbarEnabled. A value of 1 indicates that the taskbar is enabled on all displays, while 0 indicates that it is not.
The Approach
Our approach is to create a small Delphi program that:
- Checks the current state of the taskbar setting.
 - If the setting is off, it turns it on.
 - Otherwise, if the setting is on, it toggles it off and then back on to refresh the taskbar.
 
Additionally, we ensure that the changes take effect immediately by sending system messages to refresh the taskbar:
Delphi Program
Here’s a detailed walkthrough of our Delphi program:
program ToggleTaskbarOnAllDisplays;
{$APPTYPE CONSOLE}
uses
  System.SysUtils, Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, System.Win.Registry;
const
  REG_PATH = 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced';
  REG_VALUE = 'MMTaskbarEnabled';
  WM_WININICHANGE = $001A;
procedure RefreshTaskbar;
begin
  // Broadcast a message to all windows to indicate that the system settings have changed
  SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, LPARAM(PChar('Policy')));
  // Send the same message directly to the taskbar window to ensure it refreshes
  SendMessage(FindWindow('Shell_TrayWnd', nil), WM_WININICHANGE, 0, LPARAM(PChar('Policy')));
end;
procedure ToggleTaskbarSetting;
var
  Reg: TRegistry;
  CurrentValue: DWORD;
begin
  // Create a TRegistry object to access the Windows registry
  Reg := TRegistry.Create(KEY_READ or KEY_WRITE);
  try
    Reg.RootKey := HKEY_CURRENT_USER; // Set the root key to HKEY_CURRENT_USER
    // Open the registry key where the taskbar setting is stored
    if Reg.OpenKey(REG_PATH, False) then
    begin
      // Check if the value exists in the registry
      if Reg.ValueExists(REG_VALUE) then
      begin
        // Read the current value of the taskbar setting
        CurrentValue := Reg.ReadInteger(REG_VALUE);
        Writeln('Current MMTaskbarEnabled value: ', CurrentValue);
        if CurrentValue = 0 then
        begin
          // If the setting is currently OFF (0), set it to ON (1)
          Reg.WriteInteger(REG_VALUE, DWORD(1));
          // Notify the system that an association has changed
          Winapi.ShlObj.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
          Sleep(500); // Brief delay to ensure the change is applied
          // Notify the system again to ensure the change is propagated
          Winapi.ShlObj.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
          // Refresh the taskbar to apply the change immediately
          RefreshTaskbar;
          Writeln('Taskbar setting turned ON successfully.');
        end
        else
        begin
          // If the setting is currently ON (1), set it to OFF (0)
          Reg.WriteInteger(REG_VALUE, DWORD(0));
          // Notify the system that an association has changed
          Winapi.ShlObj.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
          Sleep(1000); // Wait for 1 second
          // Set the setting back to ON (1)
          Reg.WriteInteger(REG_VALUE, DWORD(1));
          // Notify the system again to ensure the change is applied
          Winapi.ShlObj.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
          Sleep(500); // Brief delay to ensure the change is applied
          // Notify the system again to ensure the change is propagated
          Winapi.ShlObj.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
          // Refresh the taskbar to apply the change immediately
          RefreshTaskbar;
          Writeln('Taskbar setting toggled OFF and back ON successfully.');
        end;
      end
      else
        Writeln('Registry value does not exist'); // If the value does not exist in the registry
      Reg.CloseKey; // Close the registry key
    end
    else
      Writeln('Failed to open registry key'); // If the registry key could not be opened
  finally
    Reg.Free; // Free the TRegistry object
  end;
end;
begin
  try
    ToggleTaskbarSetting; // Call the procedure to toggle the taskbar setting
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message); // Handle any exceptions that occur
  end;
  Readln; // Wait for user input before closing the console window
end.
Explanation of the Code
- Registry Access: The program uses the 
TRegistry class to read and write the MMTaskbarEnabled value. - Toggle Logic: It checks the current value and toggles it accordingly.
 - Refresh Mechanism: To ensure the changes take effect immediately, we use the 
SHChangeNotify function and send the WM_WININICHANGE message to refresh the system settings. - Delay Handling: Adding slight delays (
Sleep) ensures the system has enough time to apply the changes properly. 
Understanding SHChangeNotify and WM_WININICHANGE
When working with Windows settings, especially those related to the user interface like the taskbar, it's crucial to notify the system about any changes. Two essential tools for this purpose are the SHChangeNotify function and the WM_WININICHANGE message.
SHChangeNotify
The SHChangeNotify function is used to notify the system of changes to the shell's state. This can include changes to file associations, the contents of folders, or other system settings. When you call SHChangeNotify, you inform the shell that it needs to refresh its state or update its display.
Syntax:
procedure SHChangeNotify(wEventId: Longint; uFlags: UINT; dwItem1, dwItem2: Pointer); stdcall;
Parameters:
wEventId: Indicates the type of change event that occurred. Common values include SHCNE_ASSOCCHANGED to notify that a file type association has changed.uFlags: Specifies the flags that indicate the meaning of the dwItem1 and dwItem2 parameters. Common flags include SHCNF_IDLIST, SHCNF_PATH, etc.dwItem1 and dwItem2: Additional parameters whose meanings are defined by wEventId and uFlags.
In our context, calling SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil) signals to the system that an association change has occurred, prompting it to refresh the taskbar settings.
WM_WININICHANGE
The WM_WININICHANGE message is sent to all top-level windows when a change is made to the system's configuration. This message allows applications to update their settings in response to changes in the system configuration.
Syntax:
const
  WM_WININICHANGE = $001A;
Parameters
wParam: Specifies whether the message is sent because of a system-wide change (using 0 or SPI_SETNONCLIENTMETRICS).lParam: Points to a string specifying the section of the system configuration that has changed (e.g., "Policy").
When we use SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, LPARAM(PChar('Policy')));, it broadcasts the WM_WININICHANGE message to all top-level windows, notifying them that a policy change has occurred and they should update their settings accordingly.
Why Use These Tools?
- Immediate Effect: By using 
SHChangeNotify and broadcasting WM_WININICHANGE, we ensure that the changes to the taskbar settings take effect immediately, without requiring a system restart or manual intervention. - System-Wide Update: These notifications ensure that all relevant components and applications are informed of the changes, leading to a consistent and updated system state.
 
Conclusion
Using SHChangeNotify and WM_WININICHANGE together provides a powerful way to manage and apply system settings changes programmatically. This approach is essential for automating adjustments and ensuring that the system's user interface reflects the latest configurations immediately.
Summary
By automating the toggling of the taskbar setting with Delphi, you can save time and avoid the hassle of manual configuration every time you start Windows. This approach demonstrates the power of Delphi in interacting with system settings and enhancing user experience in multi-monitor setups.
Feel free to adapt and expand this program for other system settings or tasks you want to automate. Happy coding!