인터넷/IT/프로그래밍

jvcl - TJvXPMenuItemPainter 에 짜증나는 버그

coffee94 2014. 3. 7. 09:00

해당 버그 현상은 일단 다음과 같습니다. ( JVCL347CompleteJCL241-Build4571 )

메뉴를 띄우게 되면 ( 알려진 프로그램이라서 캡션은 따로 삭제하였습니다. ) 처음에 다음과 같이 나오고,



메뉴를 마우스에 가까이 가져가면..

다음과 같이 나옵니다.





각각 확대해보면 다음과 같습니다.







다른 점을 찾으셧나요?


테두리 색상이 마우스를 갔다되면 바뀌는게 이게 의도한 것인지 버그인지는 모르겠으나 일단 저의 입장에서는 버그로 보입니다.

하이라이트 개념으로 만들었다면 다시 마우스를 메뉴바깥으로 가져갈 때 원래대로 되돌아와야 하는데 그렇지는 않구요.


처음 메뉴를 띄웠을 때 기본 테두리를 그대로 가져가면 좋겠는데 그렇지가 않아서 그렇게 수정할려고 하니까 아예 jvcl 코드자체를 수정해야 되더군요.


삽질을 방지하기 위해서 수정한 부분을 적어보면 다음과 같습니다.




아래 함수가 수정되었는데 마우스를 가져갔을 때 테두리가 그려지는 함수자체를 호출하지 않도록 하면 메뉴를 선택했을 때 하이라이트 되는 테두리가 제대로 그려지지 않아서 수정합니다.

procedure TJvXPMenuItemPainter.DrawSelectedFrame(ARect: TRect);
begin
  with Canvas do
  begin
    Font.Color := clMenuText;
    if IsPopup(FItem) then
    begin
      Brush.Assign(SelectionFrameBrush);
      Pen.Style := psClear;
      Rectangle(1, ARect.Top, ARect.Right + 4, ARect.Bottom - 1);
      Pen.Assign(SelectionFramePen);
      Brush.Style := bsClear;

      MoveTo(1, ARect.Top);
      LineTo(ARect.Right + 4, ARect.Top);
      MoveTo(1, ARect.Bottom - 2);
      LineTo(ARect.Right + 4, ARect.Bottom - 2);

      MoveTo(1, ARect.Top);
      LineTo(1, ARect.Bottom - 2);
      MoveTo(ARect.Right - 1, ARect.Top);
      LineTo(ARect.Right - 1, ARect.Bottom - 2);

    end
    else
    begin
      Brush.Color := clSilver;
      Brush.Style := bsSolid;
      Pen.Color := clGray;
      Pen.Style := psSolid;
      Rectangle(ARect);
    end;
  end;
end;



그리고 아래 부분은 마우스를 가져갔을 때 테두리가 안 그려지도록 하는 부분입니다.

procedure TJvXPMenuItemPainter.Paint(Item: TMenuItem; ItemRect: TRect;
  State: TMenuOwnerDrawState);
var
  CanvasWindow: HWND;
  WRect: TRect;
  DefProc: Pointer;
  TmpDC: hDC;
begin
  FItem := Item;

  // draw the contour of the window
  if IsPopup(Item) and not(csDesigning in ComponentState) then
  begin
    CanvasWindow := WindowFromDC(Canvas.Handle);

    if not(Assigned(FMainMenu) and (FMainMenu.GetOwner <> nil) and
      (FMainMenu.GetOwner is TForm) and
      (TForm(FMainMenu.GetOwner).Handle = CanvasWindow)) then
    begin
      // If we have a window, that has a WndProc, which is different from our
      // replacement WndProc and we are not at design time, then install
      // our replacement WndProc.
      // Once this is done, we can draw the border in the appropriate rect.

      // Note that if the menu has sub-menus we can have multiple hooks; so we
      // use TWindowList
      if CanvasWindow <> 0 then
      begin
        GetWindowRect(CanvasWindow, WRect);

        DefProc := Pointer(GetWindowLongPtr(CanvasWindow, GWL_WNDPROC));
        if (DefProc <> nil) and (DefProc <> @XPMenuItemPainterWndProc) and
          not(csDesigning in Menu.ComponentState) then
        begin
          currentXPPainter := Self;
          // 현재 메뉴에 전체 보더를 그리지 않도록 수정해놨는데
          // 이 부분에서 서브 메뉴에 의해서 탑 메뉴가 가려진 후 다시 나타날 때
          // XPMenuItemPainterWndProc 호출되는데 간혹 이 부분에서 보더가 그려지는
          // 문제가 있어서 주석처리합니다.
          // WindowList.AddHook(CanvasWindow, DefProc, @XPMenuItemPainterWndProc);
        end;

        (* // Note: we draw the border here. But using the "Canvas" property is
          // not good enough as it does not take into account the borders of the
          // menu. So for version prior to Vista, be draw directly on the desktop
          // window canvas. However, with desktop composition under Vista, this
          // is awfully slow so we try to use the DISPLAY device context. Note
          // that the behaviour on Vista has not been tested as no JVCL developper
          // has access to a Vista system with the Aero them turned on.
          if JclSysInfo.GetWindowsVersion = wvWinVista then
          begin
          TmpDC := CreateDC('DISPLAY', nil, nil, nil);
          try
          if not Assigned(FBorderCanvas) then
          FBorderCanvas := TCanvas.Create;

          FBorderCanvas.Handle := TmpDC;
          DrawBorder(FBorderCanvas, WRect);
          finally
          DeleteDC(TmpDC);
          end;
          end
          else *)
        begin
          if not Assigned(FBorderCanvas) then
            FBorderCanvas := TCanvas.Create;

          TmpDC := GetWindowDC(CanvasWindow);
          try
            FBorderCanvas.Handle := TmpDC;
            // DrawBorder(FBorderCanvas, WRect);
          finally
            FBorderCanvas.Handle := 0;
            ReleaseDC(CanvasWindow, TmpDC);
          end;
        end;
      end;
    end;
  end;

  // then draw the items
  inherited Paint(Item, ItemRect, State);
end;



소스를 수정하고 저장한다고 해서 바로 적용되는 것은 아닙니다.

왜냐면 기본적으로 pas를 참조하는게 아니라  dcu를 참조하기 때문에 jvMenus.duc를 따로 찾아서 삭제하고 수정된  jvMenus.Pas를 참조하도록 변경해야 합니다.


전체코드는 다음과 같습니다.