Pages

Friday, 5 July 2024

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 distracting the user with an unnecessary command prompt. Fortunately, Delphi provides a straightforward way to achieve this. Below, we'll walk through the steps to create a console application that runs silently without displaying the console window.

program OpenTaskbarSettings;


// The $APPTYPE GUI directive ensures that the application is treated as a GUI application,

// which prevents the console window from being shown.

{$APPTYPE GUI}


uses

  System.SysUtils,

  Winapi.Windows,

  Winapi.ShellAPI;


// Procedure to hide the console window if it exists

procedure HideConsole;

var

  hConsole: HWND;

begin

  // Get the handle of the console window

  hConsole := Winapi.Windows.GetConsoleWindow;

  // If the console window handle is valid, hide the console window

  if hConsole <> 0 then

    ShowWindow(hConsole, SW_HIDE);

end;


// Procedure to open the Taskbar Settings window

procedure DoOpenTaskbarSettings;

begin

  // Use ShellExecute to open the Taskbar Settings window

  Winapi.ShellAPI.ShellExecute(0, 'open', 'ms-settings:taskbar', nil, nil, SW_SHOWNORMAL);

end;


begin

  // Hide the console window immediately upon startup

  HideConsole;

  try

    // Open the Taskbar Settings window

    DoOpenTaskbarSettings;

  except

    // Handle any exceptions that occur

    on E: Exception do

      Writeln(E.ClassName, ': ', E.Message);

  end;

end.


Explanation of the Code:

  1. // Remove: {$APPTYPE CONSOLE}, instead add:

  2. {$APPTYPE GUI}: The $APPTYPE GUI directive* ensures that the application is treated as a GUI application, which prevents the console window from being shown.

  3. HideConsole Procedure:

    • GetConsoleWindow: Retrieves the handle of the console window.
    • ShowWindow: Hides the console window if the handle is valid.
  4. DoOpenTaskbarSettings Procedure:

    • ShellExecute: Uses the ShellExecute function to open the Taskbar Settings window.
  5. Main Block:

    • HideConsole: Hides the console window immediately upon startup.
    • DoOpenTaskbarSettings: Opens the Taskbar Settings window.
    • try...except: Handles any exceptions that may occur during the execution.

This setup will create a console application that hides its console window immediately upon startup, so the console window will not be visible at all while the application runs.

Steps to Implement:

  1. Open Delphi.
  2. Create a new Console Application.
  3. Replace the default code with the above code.
  4. Run the application.

_______________

* A "directive" in programming is a special instruction that tells the compiler how to process the code.

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!

Monday, 24 June 2024

Sierpinski Triangle Fractal version 2.0

 This is a substantial update from yesterday's article Creating a Fractal Art Application: Drawing the Sierpinski Triangle.

What's new? Here are the new features:

1. A new menu item with a shortcut to set random colors:


This command first creates a random foreground color and then sets the background color as a contrasting hue:


// Random Colors:


function GetRandomColor: TColor;

begin

  Result := Winapi.Windows.RGB(Random(256), Random(256), Random(256));

end;


type

  TRGB = record

    R, G, B: Byte;

  end;


  THSV = record

    H, S, V: Double;

  end;


function RGBToHSV(Color: TColor): THSV;

var

  R, G, B: Byte;

  MinVal, MaxVal, Delta: Double;

begin

  // Extract the RGB components from the TColor value

  Color := ColorToRGB(Color);

  R := GetRValue(Color);

  G := GetGValue(Color);

  B := GetBValue(Color);


  // Find the minimum and maximum values among R, G, and B

  MinVal := Min(Min(R, G), B);

  MaxVal := Max(Max(R, G), B);


  // Value (V) is the maximum of R, G, and B divided by 255

  Result.V := MaxVal / 255;

  Delta := MaxVal - MinVal;


  // Saturation (S) is zero if the maximum value is zero, otherwise it's Delta divided by the maximum value

  if MaxVal = 0 then

    Result.S := 0

  else

    Result.S := Delta / MaxVal;


  // If Delta is zero, then the color is a shade of gray and the hue is undefined (set to 0)

  if Delta = 0 then

    Result.H := 0

  else

  begin

    // Calculate the Hue (H)

    if R = MaxVal then

      Result.H := (G - B) / Delta

    else if G = MaxVal then

      Result.H := 2 + (B - R) / Delta

    else

      Result.H := 4 + (R - G) / Delta;


    // Convert the Hue to degrees

    Result.H := Result.H * 60;

    if Result.H < 0 then

      Result.H := Result.H + 360;

  end;

end;


function HSVToRGB(HSV: THSV): TColor;

var

  R, G, B: Byte;

  i: Integer;

  f, p, q, t: Double;

begin

  if HSV.S = 0 then

  begin

    // If Saturation is zero, the color is a shade of gray

    R := Round(HSV.V * 255);

    G := R;

    B := R;

  end

  else

  begin

    // Calculate the sector of the color wheel (0 to 5)

    HSV.H := HSV.H / 60;

    i := Floor(HSV.H);

    f := HSV.H - i; // Fractional part of the sector

    p := HSV.V * (1 - HSV.S);

    q := HSV.V * (1 - HSV.S * f);

    t := HSV.V * (1 - HSV.S * (1 - f));


    // Assign the RGB values based on the sector

    case i of

      0:

        begin

          R := Round(HSV.V * 255);

          G := Round(t * 255);

          B := Round(p * 255);

        end;

      1:

        begin

          R := Round(q * 255);

          G := Round(HSV.V * 255);

          B := Round(p * 255);

        end;

      2:

        begin

          R := Round(p * 255);

          G := Round(HSV.V * 255);

          B := Round(t * 255);

        end;

      3:

        begin

          R := Round(p * 255);

          G := Round(q * 255);

          B := Round(HSV.V * 255);

        end;

      4:

        begin

          R := Round(t * 255);

          G := Round(p * 255);

          B := Round(HSV.V * 255);

        end;

      else

        begin

          R := Round(HSV.V * 255);

          G := Round(p * 255);

          B := Round(q * 255);

        end;

    end;

  end;


  // Combine the RGB components into a TColor value

  Result := RGB(R, G, B);

end;


function GetContrastingColor(Color: TColor): TColor;

var

  HSV: THSV;

begin

  // Convert the given color from RGB to HSV

  HSV := RGBToHSV(Color);


  // Adjust the Hue by 180 degrees to get the complementary color

  HSV.H := HSV.H + 180;

  if HSV.H > 360 then

    HSV.H := HSV.H - 360;


  // Convert the adjusted HSV color back to RGB

  Result := HSVToRGB(HSV);

end;


procedure TForm1.mSetRandomColorsClick(Sender: TObject);

var

  RandomColor, ContrastingColor: TColor;

begin

  // Generate a random color

  RandomColor := GetRandomColor;


  // Get the contrasting color for the generated random color

  ContrastingColor := GetContrastingColor(RandomColor);


  // Set the new random foreground and background colors

  FForegroundColor := RandomColor;

  FBackgroundColor := ContrastingColor;


  // Redraw the triangle with the new colors

  UpdateBuffer;

end;


2. New menu items with a shortcut to decrease or increase the recursion depth:


These commands decrease the recursion depth of the Sierpinski Triangle to a minimum of 1 or increase the recursion depth of the Sierpinski Triangle to a maximum of 8, respectively:


procedure TForm1.mRecursionDepthDecClick(Sender: TObject);

begin

  if FRecursionDepth > 1 then

  begin

    // Decrease the recursion depth:

    Dec(FRecursionDepth);

    UpdateBuffer;

  end;

end;


procedure TForm1.mRecursionDepthIncClick(Sender: TObject);

begin

  if FRecursionDepth < 8 then

  begin

    // Increase the recursion depth:

    Inc(FRecursionDepth);

    UpdateBuffer;

  end;

end;


3. A new menu item to copy the Sierpinski Triangle image to the clipboard:


This command copies the Sierpinski Triangle image to the Clipboard by creating the  standard Bitmap formats and the PNG format in the clipboard:


// Clipboard:


procedure TForm1.SaveToClipboard;

var

  Bitmap: TBitmap;

  PNG: TPngImage;

  MemStream: TMemoryStream;

  HMem: THandle;

  P: Pointer;

  ClipFormat: TClipFormat;

begin

  // Open the clipboard for exclusive access

  Clipboard.Open;

  try

    // Copy the bitmap to the clipboard

    Bitmap := TBitmap.Create;

    try

      Bitmap.Assign(FBuffer);

      Clipboard.Assign(Bitmap);

    finally

      Bitmap.Free;

    end;


    // Create a PNG image from the buffer

    PNG := TPngImage.Create;

    try

      PNG.Assign(FBuffer);


      // Save the PNG to a memory stream

      MemStream := TMemoryStream.Create;

      try

        PNG.SaveToStream(MemStream);

        MemStream.Position := 0;


        // Allocate global memory for the PNG data

        HMem := GlobalAlloc(GMEM_MOVEABLE, MemStream.Size);

        if HMem = 0 then

          RaiseLastOSError;

        try

          P := GlobalLock(HMem);

          if P = nil then

            RaiseLastOSError;

          try

            MemStream.ReadBuffer(P^, MemStream.Size);

          finally

            GlobalUnlock(HMem);

          end;


          // Register the PNG clipboard format

          ClipFormat := RegisterClipboardFormat('PNG');

          if ClipFormat = 0 then

            RaiseLastOSError;


          // Set the PNG data to the clipboard

          SetClipboardData(ClipFormat, HMem);

        except

          GlobalFree(HMem);

          raise;

        end;

      finally

        MemStream.Free;

      end;

    finally

      PNG.Free;

    end;

  finally

    Clipboard.Close;

  end;

end;


procedure TForm1.mCopyClick(Sender: TObject);

begin

  SaveToClipboard;

end;




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...