• <table id="x5mq0"><track id="x5mq0"></track></table>
  • <code id="x5mq0"><nobr id="x5mq0"><sub id="x5mq0"></sub></nobr></code>

      <pre id="x5mq0"><small id="x5mq0"><p id="x5mq0"></p></small></pre>
    1. <pre id="x5mq0"><small id="x5mq0"><track id="x5mq0"></track></small></pre>

    2. <th id="x5mq0"><video id="x5mq0"></video></th>

      窗體皮膚實現 - 重繪窗體非客戶區(三)

      窗體邊框基本的繪制和控制完成,在第二篇中主要遺留的問題。

      • 標題區域圖標和按鈕沒繪制
      • 縮放時客戶區顯示有問題

      解決完上述兩個的問題,皮膚處理基本完整。

      主要內容:

      • 繪制標題區域內容
      • 標題區按鈕響應鼠標消息
      • 繪制客戶區

      繪制標題區域內容

      • 獲取標題有效區域
      • 繪制窗體圖標
      • 繪制按鈕
      • 繪制標題

      標題區域主要考慮窗體是否在最大化狀態,最大化后實際的標題繪制區域會有變化??梢酝ㄟ^ IsZoomedGetWindowLong(Handle, GWL_STYLE) and WS_MAXIMIZE = WS_MAXIMIZE 的方式獲取。

      AMaxed := IsZoomed(Handle);    // 獲取窗體最大化狀態
      
      function TTest.GetCaptionRect(AMaxed: Boolean): TRect;
      var
        rFrame: TRect;
      begin
        rFrame := GetFrameSize;         // 窗體上下左右的邊框尺寸
        // 最大化狀態簡易處理
        if AMaxed then
          Result := Rect(8, 8, FWidth - 9 , rFrame.Top)
        else
          Result := Rect(rFrame.Left, 3, FWidth - rFrame.right, rFrame.Top);
      end;
      

      獲取窗體圖標并繪制

      繪制窗體圖標稍微有些麻煩,需要獲取窗體的Icon圖標。窗體圖標并不一定是程序圖標。主要過程通過WM_GETICON 這個消息獲取圖標。

      TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_SMALL, 0));
      if TmpHandle = 0 then
          TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_BIG, 0));
      

      如果上述方法無法獲得,需要通過GetClassNameGetClassInfoEx 這2個API獲取。

      { Get instance }
      GetClassName(Handle, @Buffer, SizeOf(Buffer));
      FillChar(Info, SizeOf(Info), 0);
      Info.cbSize := SizeOf(Info);
      
      if GetClassInfoEx(GetWindowLong(Handle, GWL_HINSTANCE), @Buffer, Info) then
      begin
        TmpHandle := Info.hIconSm;
        if TmpHandle = 0 then
          TmpHandle := Info.HICON;
      end
      

      上述這2種方法還是無法獲取。那~~ 就沒有辦法了。如果非要繪制圖標可以使用Application的圖標進行代替。

      Application.Icon.Handle
      
      // 完整獲取窗體圖標的方法
      function TTest.GetIcon: TIcon;
      var
        IconX, IconY: integer;
        TmpHandle: THandle;
        Info: TWndClassEx;
        Buffer: array [0 .. 255] of Char;
      begin
        ///
        /// 獲取當前form的圖標
        /// 這個圖標和App的圖標是不同的
        TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_SMALL, 0));
        if TmpHandle = 0 then
          TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_BIG, 0));
      
        if TmpHandle = 0 then
        begin
          { Get instance }
          GetClassName(Handle, @Buffer, SizeOf(Buffer));
          FillChar(Info, SizeOf(Info), 0);
          Info.cbSize := SizeOf(Info);
      
          if GetClassInfoEx(GetWindowLong(Handle, GWL_HINSTANCE), @Buffer, Info) then
          begin
            TmpHandle := Info.hIconSm;
            if TmpHandle = 0 then
              TmpHandle := Info.HICON;
          end
        end;
      
        if FIcon = nil then
          FIcon := TIcon.Create;
      
        if TmpHandle <> 0 then
        begin
          IconX := GetSystemMetrics(SM_CXSMICON);
          if IconX = 0 then
            IconX := GetSystemMetrics(SM_CXSIZE);
          IconY := GetSystemMetrics(SM_CYSMICON);
          if IconY = 0 then
            IconY := GetSystemMetrics(SM_CYSIZE);
          FIcon.Handle := CopyImage(TmpHandle, IMAGE_ICON, IconX, IconY, 0);
          FIconHandle := TmpHandle;
        end;
      
        Result := FIcon;
      end;
      

      繪制窗體最小化、最大化和關閉按鈕

      繪制系統最小化、最大化和關閉按鈕直接使用貼圖的方法。做一張PNG圖片,做成資源文件加入到單元中。

      注:圖標是白色的沒底色看不見,所以在貼的圖上加了個黑底。

      計算好實際位置后,直接把從資源中加載的圖標繪制上去。

      procedure TTest.DrawButton(DC: HDC; AKind: TFormButtonKind; AState: TSkinIndicator; const R: TRect);
      var
        hB: HBRUSH;
        iColor: Cardinal;
        rSrcOff: TPoint;
        x, y: integer;
      begin
        /// 繪制背景
        case AState of
          siHover         : iColor := SKINCOLOR_BTNHOT;
          siPressed       : iColor := SKINCOLOR_BTNPRESSED;
          siSelected      : iColor := SKINCOLOR_BTNPRESSED;
          siHoverSelected : iColor := SKINCOLOR_BTNHOT;
        else                iColor := SKINCOLOR_BAKCGROUND;
        end;
        hB := CreateSolidBrush(iColor);
        FillRect(DC, R, hB);
        DeleteObject(hB);
      
        /// 繪制圖標
        rSrcOff := Point(SIZE_RESICON * ord(AKind), 0);
        x := R.Left + (R.Right - R.Left - SIZE_RESICON) div 2;
        y := R.Top + (R.Bottom - R.Top - SIZE_RESICON) div 2;
        DrawTransparentBitmap(FSkinData, rSrcOff.X, rSrcOff.Y, DC, x, y, SIZE_RESICON, SIZE_RESICON);
      end;
      

      最后繪制標題,設置背景SetBkMode透明,設置字體顏色SetTextColor為白色。

      /// 繪制Caption
      sData :=  GetCaption;
      SetBkMode(DC, TRANSPARENT);
      SaveColor := SetTextColor(DC, $00FFFFFF);
      Flag := DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX;
      DrawTextEx(DC, PChar(sData), Length(sData), rCaptionRect, Flag, nil);
      SetTextColor(DC, SaveColor);
      

      整個標題區域就繪制完成。

      標題區按鈕響應鼠標消息

      基本的繪制完成,鼠標滑到窗體按鈕區域(最大化、最小化和關閉)和點擊并不會相應。需要自己處理相應的消息。WM_NCHITTEST 消息是系統用來確定鼠標位置對應的窗體區域,可以通過這個消息實現對窗體按鈕的相應。

      為實現窗體按鈕的響應,只要處理這個區域。其他區域消息還是交由窗體原有消息處理。

      相應兩種狀態:

      • 滑入時的顯示樣式
      • 按下時的顯示樣式
      procedure TTest.WMNCHitTest(var Message: TWMNCHitTest);
      var
        P: TPoint;
        iHit: integer;
      begin
        // 需要把位置轉換到實際窗口位置
        P := NormalizePoint(Point(Message.XPos, Message.YPos));
      
        // 獲取 位置
        // 只對監控區域處理,其他由系統處理
        iHit := HitTest(p);
        if FHotHit > HTNOWHERE then
        begin
          Message.Result := iHit;
          Handled := True;            // 處理完成,不再交由系統處理
        end;
      
        // 響應鼠標滑入監控區域后,通知非客戶區重繪
        if iHit <> FHotHit then
        begin
          FHotHit := iHit;
          InvalidateNC;
        end;
      end;
      
      function TTest.HitTest(P: TPoint):integer;
      var
        bMaxed: Boolean;
        r: TRect;
        rCaptionRect: TRect;
        rFrame: TRect;
      begin
        Result := HTNOWHERE;
      
        ///
        /// 檢測位置
        ///
        rFrame := GetFrameSize;
        if p.Y > rFrame.Top then
          Exit;
      
        ///
        ///  只關心窗體按鈕區域
        ///
        bMaxed := IsZoomed(Handle);
        rCaptionRect := GetCaptionRect(bMaxed);
        if PtInRect(rCaptionRect, p) then
        begin
          r.Right := rCaptionRect.Right - 1;
          r.Top := 0;
          if bMaxed then
            r.Top := rCaptionRect.Top;
          r.Top := r.Top + (rFrame.Top - r.Top - SIZE_SYSBTN.cy) div 2;
          r.Left := r.Right - SIZE_SYSBTN.cx;
          r.Bottom := r.Top + SIZE_SYSBTN.cy;
      
          ///
          /// 實際繪制的按鈕就三個,其他沒處理
          ///
          if (P.Y >= r.Top) and (p.Y <= r.Bottom) and (p.X <= r.Right) then
          begin
            if (P.X >= r.Left) then
              Result := HTCLOSE
            else if p.X >= (r.Left - SIZE_SYSBTN.cx) then
              Result := HTMAXBUTTON
            else if p.X >= (r.Left - SIZE_SYSBTN.cx * 2) then
              Result := HTMINBUTTON;
          end;
        end;
      end;
      
      function HitTest(P: TPoint):integer
      

      上面代碼獲取當前鼠標所在位置,這樣滑入的Hot狀態信息已經獲取。還個是記錄按下的狀態,需要使用WM_NCLBUTTONDOWN消息獲得鼠標按下后的位置來實現。

      procedure TTest.WMNCLButtonDown(var message: TWMNCHitMessage);
      var
        iHit: integer;
      begin
        // 對監控的區域作相應
        iHit := HTNOWHERE;
        if (Message.HitTest = HTCLOSE) or (Message.HitTest = HTMAXBUTTON) or (Message.HitTest = HTMINBUTTON) or
          (Message.HitTest = HTHELP) then
        begin
          iHit := Message.HitTest;
          Message.Result := 0;
          Message.Msg := WM_NULL;
          Handled := True;           // 消息已經處理完成,不再交由系統處理
        end;
      
        // 如果按下的位置發生變化,重繪標題區
        if iHit <> FPressedHit then
        begin
          FPressedHit := iHit;
          InvalidateNC;
        end;
      end;
      

      通過上述兩個消息,獲取到鼠標所在按鈕的位置。在繪制標題區函數中直接使用。

      // 注意:
      //   按鈕樣式枚舉的順序不要顛倒,這個和資源圖標的排列順序是一致的
      TFormButtonKind = (fbkMin, fbkMax, fbkRestore, fbkClose, fbkHelp);
      
      procedure TTest.PaintNC(DC: HDC);
      const
        HITVALUES: array [TFormButtonKind] of integer = (HTMINBUTTON, HTMAXBUTTON, HTMAXBUTTON, HTCLOSE, HTHELP);
      
        function GetBtnState(AKind: TFormButtonKind): TSkinIndicator;
        begin
          // 按下區域 一定和 Hot區域一致,保證鼠標點擊到彈起的區域是一致,才能執行
          if (FPressedHit = FHotHit) and (FPressedHit = HITVALUES[AKind]) then
            Result := siPressed
          else if FHotHit = HITVALUES[AKind] then
            Result := siHover
          else
            Result := siInactive;
        end;
      
        ... ...
      begin
          ... ...
          // 繪制 關閉按鈕
          DrawButton(Dc, fbkClose, GetBtnState(fbkClose), rButton);
      
          ... ...
      end;
      

      上述的繪制相應已經完成,但鼠標點擊是不會有任何反應的。需要處理WM_NCLBUTTONUP消息

      procedure TTest.WMNCLButtonUp(var Message: TWMNCHitMessage);
      var
        iWasHit: Integer;
      begin
        iWasHit := FPressedHit;
      
        // 處理監控區域的鼠標彈起消息
        if iWasHit <> HTNOWHERE then
        begin
          FPressedHit := HTNOWHERE;
          //InvalidateNC;
      
          if iWasHit = FHotHit then
          begin
            case Message.HitTest of
              HTCLOSE     : SendMessage(Handle, WM_SYSCOMMAND, SC_CLOSE, 0);
              HTMAXBUTTON : Maximize;
              HTMINBUTTON : Minimize;
              HTHELP      : SendMessage(Handle, WM_SYSCOMMAND, SC_CONTEXTHELP, 0);
            end;
      
            Message.Result := 0;
            Message.Msg := WM_NULL;
            Handled := True;           // 消息已經處理完成,不需要控件再處理
          end;
        end;
      end;
      
      procedure TTest.Maximize;
      begin
        if Handle <> 0 then
        begin
          FPressedHit := 0;
          FHotHit := 0;
          if IsZoomed(Handle) then
            SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0)
          else
            SendMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
        end;
      end;
      
      procedure TTest.Minimize;
      begin
        if Handle <> 0 then
        begin
          FPressedHit := 0;
          FHotHit := 0;
          if IsIconic(Handle) then
            SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0)
          else
            SendMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
         end;
      end;
      

      整個標題區的消息基本處理完成,能正常相應標題區應有的功能。還有些細節上面需要處理一下,如修改窗體標題沒有及時響應。WM_SETTEXT消息用于處理標題修改。

      procedure TTest.WMSetText(var Message: TMessage);
      begin
        CallDefaultProc(Message);   // 優先有系統處理此消息
        InvalidateNC;               // 重繪標題區
        Handled := true;
      end;
      

      繪制客戶區

      還有最后一個問題。在縮放窗體時,客戶區慘不忍睹。其實這個還是比較簡單,處理擦除背景(WM_ERASEBKGND)和響應繪制(WM_PAINT)消息就能完成。

      擦除處理

      procedure TTest.WMEraseBkgnd(var message: TWMEraseBkgnd);
      var
        DC: HDC;
        SaveIndex: integer;
      begin
        DC := Message.DC;
        if DC <> 0 then
        begin
          // 如果是容器控件,擦除一定要處理。填色也行。
          // 否則會出現因主繪制延遲,出現短暫的未刷新色塊殘留。特別在使用Buffer方式繪制時常出現
          SaveIndex := SaveDC(DC);
          PaintBackground(DC);
          RestoreDC(DC, SaveIndex);
        end;
      
        Handled := True;       // 消息處理完成,控件不再處理
        Message.Result := 1;   // 繪制結束,外部不用處理
      end;
      

      繪制客戶區,需要通知子控件刷新。

      procedure TTest.WMPaint(var message: TWMPaint);
      var
        DC, hPaintDC: HDC;
        cBuffer: TBitmap;
        PS: TPaintStruct;
      begin
        ///
        /// 繪制客戶區域
        ///
        DC := Message.DC;
      
        hPaintDC := DC;
        if DC = 0 then
          hPaintDC := BeginPaint(Handle, PS);
      
        if DC = 0 then
        begin
          /// 緩沖模式繪制,減少閃爍
          cBuffer := TBitmap.Create;
          try
            cBuffer.SetSize(FWidth, FHeight);
            PaintBackground(cBuffer.Canvas.Handle);
            Paint(cBuffer.Canvas.Handle);
            /// 通知子控件進行繪制
            /// 主要是些圖形控件的重繪制(如TShape),否則??吭贔orm上的圖像控件無法正常顯示
            if Control is TWinControl then
              TacWinControl(Control).PaintControls(cBuffer.Canvas.Handle, nil);
            BitBlt(hPaintDC, 0, 0, FWidth, FHeight, cBuffer.Canvas.Handle, 0, 0, SRCCOPY);
          finally
            cBuffer.Free;
          end;
        end
        else
        begin
          Paint(hPaintDC);
          // 通知子控件重繪
          if Control is TWinControl then
            TacWinControl(Control).PaintControls(hPaintDC, nil);
        end;
      
        if DC = 0 then
          EndPaint(Handle, PS);
      
        Handled := True;
      end;
      

      其中的Paint不需要處理任何代碼。

      procedure TTest.Paint(DC: HDC);
      begin
        // 不需要處理。
      end;
      

      基本的窗體繪制控制基本完成。

      大致的效果, GIF中TShape的顏色表現有些問題,實際是正常的。

      現在時下流行的換膚,還是比較容易實現。增加一塊背景圖資源,在繪制時算好位置貼上去就OK。還有一些鼠標滑入按鈕的漸變效果,可以創建一個時鐘記錄每個按鈕的背景褪色值(透明度)使用AlphaBlend 這個函數進行繪制,或是用混色的方法處理。

      通過透明度控制背景動畫效果,參考DrawTransparentBitmap

      procedure DrawTransparentBitmap(Source: TBitmap; sx, sy: Integer; Destination: HDC;
        const dX, dY: Integer;  w, h: Integer; const Opacity: Byte = 255); overload;
      var
        BlendFunc: TBlendFunction;
      begin
        BlendFunc.BlendOp := AC_SRC_OVER;
        BlendFunc.BlendFlags := 0;
        BlendFunc.SourceConstantAlpha := Opacity;
      
        if Source.PixelFormat = pf32bit then
          BlendFunc.AlphaFormat := AC_SRC_ALPHA
        else
          BlendFunc.AlphaFormat := 0;
      
        AlphaBlend(Destination, dX, dY, w, h, Source.Canvas.Handle, sx, sy, w, h, BlendFunc);
      end;
      

      感覺XE3有些傷不起,Release版本的exe竟然要2.42M。哎~??磥硪銈€C版的。

      相關API和消息:

      • IsZoomed --- 窗體是否最大化
      • GetClassInfoEx --- 獲取窗體圖標
      • WM_GETICON --- 獲取窗體圖標
      • DrawTransparentBitmap --- 繪制透明圖片
      • GetWindowLong --- 獲取窗體信息
      • DrawIconEx --- 繪制ICON
      • SetBkMode --- 設置字體繪制背景
      • SetTextColor --- 設置字體繪制顏色

      開發環境:

      • XE3
      • win7

      源代碼:


      蘑菇房 GDI 相關文章

      亚洲成A∨人片在线观看无码
    3. <table id="x5mq0"><track id="x5mq0"></track></table>
    4. <code id="x5mq0"><nobr id="x5mq0"><sub id="x5mq0"></sub></nobr></code>

        <pre id="x5mq0"><small id="x5mq0"><p id="x5mq0"></p></small></pre>
      1. <pre id="x5mq0"><small id="x5mq0"><track id="x5mq0"></track></small></pre>

      2. <th id="x5mq0"><video id="x5mq0"></video></th>