Make Disabled Menu and Toolbar Images look better?

前端 未结 5 1838
野的像风
野的像风 2020-12-04 20:00

Please see the attached screenshot which illustrates a TToolBar from one of my programs:

\"enter

相关标签:
5条回答
  • 2020-12-04 20:44

    Sometime Ago i wrote a patch to fix this behavior. the key is patch the code of the TCustomImageList.DoDraw function, the technique used is similar to the used by the delphi-nice-toolbar app, but instead of patch a bpl IDE in this case we patch the function in memory.

    Just include this unit in your project

    unit uCustomImageDrawHook;
    
    interface
    
    uses
      Windows,
      SysUtils,
      Graphics,
      ImgList,
      CommCtrl,
      Math;
    
    implementation
    
    type
      TJumpOfs = Integer;
      PPointer = ^Pointer;
    
      PXRedirCode = ^TXRedirCode;
      TXRedirCode = packed record
        Jump: Byte;
        Offset: TJumpOfs;
      end;
    
      PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
      TAbsoluteIndirectJmp = packed record
        OpCode: Word;
        Addr: PPointer;
      end;
    
    
      TCustomImageListHack = class(TCustomImageList);
    
    var
      DoDrawBackup   : TXRedirCode;
    
    function GetActualAddr(Proc: Pointer): Pointer;
    begin
      if Proc <> nil then
      begin
        if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
          Result := PAbsoluteIndirectJmp(Proc).Addr^
        else
          Result := Proc;
      end
      else
        Result := nil;
    end;
    
    procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
    var
      n: DWORD;
      Code: TXRedirCode;
    begin
      Proc := GetActualAddr(Proc);
      Assert(Proc <> nil);
      if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
      begin
        Code.Jump := $E9;
        Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
        WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
      end;
    end;
    
    procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
    var
      n: Cardinal;
    begin
      if (BackupCode.Jump <> 0) and (Proc <> nil) then
      begin
        Proc := GetActualAddr(Proc);
        Assert(Proc <> nil);
        WriteProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n);
        BackupCode.Jump := 0;
      end;
    end;
    
    
    procedure Bitmap2GrayScale(const BitMap: TBitmap);
    type
      TRGBArray = array[0..32767] of TRGBTriple;
      PRGBArray = ^TRGBArray;
    var
      x, y, Gray: Integer;
      Row       : PRGBArray;
    begin
      BitMap.PixelFormat := pf24Bit;
      for y := 0 to BitMap.Height - 1 do
      begin
        Row := BitMap.ScanLine[y];
        for x := 0 to BitMap.Width - 1 do
        begin
          Gray             := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
          Row[x].rgbtRed   := Gray;
          Row[x].rgbtGreen := Gray;
          Row[x].rgbtBlue  := Gray;
        end;
      end;
    end;
    
    
    //from ImgList.GetRGBColor
    function GetRGBColor(Value: TColor): DWORD;
    begin
      Result := ColorToRGB(Value);
      case Result of
        clNone:
          Result := CLR_NONE;
        clDefault:
          Result := CLR_DEFAULT;
      end;
    end;
    
    
    procedure New_Draw(Self: TObject; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean);
    var
      MaskBitMap : TBitmap;
      GrayBitMap : TBitmap;
    begin
      with TCustomImageListHack(Self) do
      begin
        if not HandleAllocated then Exit;
        if Enabled then
          ImageList_DrawEx(Handle, Index, Canvas.Handle, X, Y, 0, 0, GetRGBColor(BkColor), GetRGBColor(BlendColor), Style)
        else
        begin
          GrayBitMap := TBitmap.Create;
          MaskBitMap := TBitmap.Create;
          try
            GrayBitMap.SetSize(Width, Height);
            MaskBitMap.SetSize(Width, Height);
            GetImages(Index, GrayBitMap, MaskBitMap);
            Bitmap2GrayScale(GrayBitMap);
            BitBlt(Canvas.Handle, X, Y, Width, Height, MaskBitMap.Canvas.Handle, 0, 0, SRCERASE);
            BitBlt(Canvas.Handle, X, Y, Width, Height, GrayBitMap.Canvas.Handle, 0, 0, SRCINVERT);
          finally
            GrayBitMap.Free;
            MaskBitMap.Free;
          end;
        end;
      end;
    end;
    
    procedure HookDraw;
    begin
      HookProc(@TCustomImageListHack.DoDraw, @New_Draw, DoDrawBackup);
    end;
    
    procedure UnHookDraw;
    begin
      UnhookProc(@TCustomImageListHack.DoDraw, DoDrawBackup);
    end;
    
    
    initialization
     HookDraw;
    finalization
     UnHookDraw;
    end.
    

    and the result will be

    enter image description here

    0 讨论(0)
  • 2020-12-04 20:53

    I submitted a QC report for a related issue over a year ago, but that was for menus. I've never seen this for TToolbar since it is a wrapper to the common control and the drawing is handled by Windows.

    However, the images you are seeing are clearly as result of the VCL calling TImageList.Draw and passing Enabled=False – nothing else looks that bad! Are you 100% sure this really is a TToolbar?

    The fix will surely be to avoid TImageList.Draw and call ImageList_DrawIndirect with the ILS_SATURATE.

    You may need to modify some VCL source. First find the location where the toolbar is being custom drawn and call this routine instead of the calls to TImageList.Draw.

    procedure DrawDisabledImage(DC: HDC; ImageList: TCustomImageList; Index, X, Y: Integer);
    var
      Options: TImageListDrawParams;
    begin
      ZeroMemory(@Options, SizeOf(Options));
      Options.cbSize := SizeOf(Options);
      Options.himl := ImageList.Handle;
      Options.i := Index;
      Options.hdcDst := DC;
      Options.x := X;
      Options.y := Y;
      Options.fState := ILS_SATURATE;
      ImageList_DrawIndirect(@Options);
    end;
    

    An even better fix would be to work out why the toolbar is being custom drawn and find a way to let the system do it.


    EDIT 1

    I've looked at the Delphi source code and I'd guess that you are custom drawing the toolbar, perhaps because it has a gradient. I never even knew that TToolbar could handle custom drawing but I'm just a plain vanilla kind of guy!

    Anyway, I can see code in TToolBar.GradientDrawButton calling the TImageList.Draw so I think the explanation above is on the right track.

    I'm fairly sure that calling my DrawDisabledImage function above will give you better results. If could find a way to make that happen when you call TImageList.Draw then that would, I suppose, be the very best fix since it would apply wholesale.

    EDIT 2

    Combine the function above with @RRUZ's answer and you have an excellent solution.

    0 讨论(0)
  • 2020-12-04 20:56

    Solution from @RRUZ dosn't work if you use LargeImages in ActionToolBar. I made changes to the @RRUZ code to work with LargeImages in ActionToolBar.

    unit unCustomImageDrawHook;
    
    interface
    
    uses
      Windows,
      SysUtils,
      Graphics,
      ImgList,
      CommCtrl,
      Math,
      Vcl.ActnMan,
      System.Classes;
    
    implementation
    
    type
      TJumpOfs = Integer;
      PPointer = ^Pointer;
    
      PXRedirCode = ^TXRedirCode;
      TXRedirCode = packed record
        Jump: Byte;
        Offset: TJumpOfs;
      end;
    
      PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
      TAbsoluteIndirectJmp = packed record
        OpCode: Word;
        Addr: PPointer;
      end;
    
    
      TCustomImageListHack = class(TCustomImageList);
      TCustomActionControlHook = class(TCustomActionControl);
    
    var
      DoDrawBackup   : TXRedirCode;
      DoDrawBackup2   : TXRedirCode;  
    
    function GetActualAddr(Proc: Pointer): Pointer;
    begin
      if Proc <> nil then
      begin
        if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
          Result := PAbsoluteIndirectJmp(Proc).Addr^
        else
          Result := Proc;
      end
      else
        Result := nil;
    end;
    
    procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
    var
      n: SIZE_T;
      Code: TXRedirCode;
    begin
      Proc := GetActualAddr(Proc);
      Assert(Proc <> nil);
      if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
      begin
        Code.Jump := $E9;
        Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
        WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
      end;
    end;
    
    procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
    var
      n: SIZE_T;
    begin
      if (BackupCode.Jump <> 0) and (Proc <> nil) then
      begin
        Proc := GetActualAddr(Proc);
        Assert(Proc <> nil);
        WriteProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n);
        BackupCode.Jump := 0;
      end;
    end;
    
    procedure Bitmap2GrayScale(const BitMap: TBitmap);
    type
      TRGBArray = array[0..32767] of TRGBTriple;
      PRGBArray = ^TRGBArray;
    var
      x, y, Gray: Integer;
      Row       : PRGBArray;
    begin
      BitMap.PixelFormat := pf24Bit;
      for y := 0 to BitMap.Height - 1 do
      begin
        Row := BitMap.ScanLine[y];
        for x := 0 to BitMap.Width - 1 do
        begin
          Gray             := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
          Row[x].rgbtRed   := Gray;
          Row[x].rgbtGreen := Gray;
          Row[x].rgbtBlue  := Gray;
        end;
      end;
    end;
    
    
    //from ImgList.GetRGBColor
    function GetRGBColor(Value: TColor): DWORD;
    begin
      Result := ColorToRGB(Value);
      case Result of
        clNone:
          Result := CLR_NONE;
        clDefault:
          Result := CLR_DEFAULT;
      end;
    end;
    
    
    procedure New_Draw(Self: TObject; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean);
    var
      MaskBitMap : TBitmap;
      GrayBitMap : TBitmap;
    begin
      with TCustomImageListHack(Self) do
      begin
        if not HandleAllocated then Exit;
        if Enabled then
          ImageList_DrawEx(Handle, Index, Canvas.Handle, X, Y, 0, 0, GetRGBColor(BkColor), GetRGBColor(BlendColor), Style)
        else
        begin
          GrayBitMap := TBitmap.Create;
          MaskBitMap := TBitmap.Create;
          try
            GrayBitMap.SetSize(Width, Height);
            MaskBitMap.SetSize(Width, Height);
            GetImages(Index, GrayBitMap, MaskBitMap);
            Bitmap2GrayScale(GrayBitMap);
            BitBlt(Canvas.Handle, X, Y, Width, Height, MaskBitMap.Canvas.Handle, 0, 0, SRCERASE);
            BitBlt(Canvas.Handle, X, Y, Width, Height, GrayBitMap.Canvas.Handle, 0, 0, SRCINVERT);
          finally
            GrayBitMap.Free;
            MaskBitMap.Free;
          end;
        end;
      end;
    end;
    
    
    procedure New_Draw2(Self: TObject; const Location: TPoint);
    var
      ImageList: TCustomImageList;
      DrawEnabled: Boolean;
      LDisabled: Boolean;
    begin
      with TCustomActionControlHook(Self) do
      begin
        if not HasGlyph then Exit;
        ImageList := FindImageList(True, LDisabled, ActionClient.ImageIndex);
        if not Assigned(ImageList) then Exit;
        DrawEnabled := LDisabled or Enabled and (ActionClient.ImageIndex <> -1) or
          (csDesigning in ComponentState);
        ImageList.Draw(Canvas, Location.X, Location.Y, ActionClient.ImageIndex,
          dsTransparent, itImage, DrawEnabled);
      end;
    end;
    
    
    procedure HookDraw;
    begin
      HookProc(@TCustomImageListHack.DoDraw, @New_Draw, DoDrawBackup);
      HookProc(@TCustomActionControlHook.DrawLargeGlyph, @New_Draw2, DoDrawBackup2);
    end;
    
    procedure UnHookDraw;
    begin
      UnhookProc(@TCustomImageListHack.DoDraw, DoDrawBackup);
      UnhookProc(@TCustomActionControlHook.DrawLargeGlyph, DoDrawBackup2);
    end;
    
    
    initialization
      HookDraw;
    finalization
      UnHookDraw;
    end.
    
    0 讨论(0)
  • 2020-12-04 20:58

    Use TActionToolbar , TActionmanager , Timagelist

    Set action managers image list to a Timagelist. and set Disabledimages to another imagelist

    0 讨论(0)
  • 2020-12-04 21:04

    Take a look at this Delphi IDE fix. Maybe you can mimic it's implementation.

    0 讨论(0)
提交回复
热议问题