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!