Pages

Thursday 4 July 2024

Automating Taskbar Settings on Multiple Displays

 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:

  1. Checks the current state of the taskbar setting.
  2. If the setting is off, it turns it on.
  3. 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?

  1. 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.
  2. 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!

No comments:

Post a Comment

How to Run a Silent Console Application in Delphi

 Author: PETER ASCHBACHER (PA-SOFT) Sometimes, it's necessary to hide the console window of a CONSOLE APPLICATION entirely to avoid dist...