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

      窗體皮膚實現 - 在標題欄上增加快速工具條(四)

      前面都是繪制非客戶區的基礎工作。繪制非客戶區一般的目的,都是想在上面做文章。前面一堆廢話就是想在標題區域增加快速工具條。

      前續的基礎工作完成,想要在標題區域增加特殊區域都非常方便。只要在繪制時控制自定義區域需要占用標題區域多少空間,然后直接在所占位置繪制。做這個事情前,稍微把代碼規整了下。所以界面皮膚處理放到一個單元中。

      主要處理步驟:
      1、劃出一個新區域(整個工具條作為一個區域)
      2、處理區域檢測(HitTest)
      3、如果是新區域,把相應消息傳給這個區域處理。
      4、響應鼠標點擊,執行Action

      通過上述步驟就能擴展出所想要的標題區快速工具條的。

      模塊化表區域工具條

      標題按鈕區域是作為一個整體處理,這樣比較容易控制和擴展。只要當檢測區域是標題工具區時,消息交由工具條實現。

      這樣做的好處就是,簡化自定義皮膚TskForm內部的處理。模塊化比較清晰,簡化實現邏輯。

      HTCUSTOM = 100; //HTHELP + 1;       /// 自定義區域ID
      HTCAPTIONTOOLBAR = HTCUSTOM + 1;    /// 標題工具區域ID
      
      /// 檢測區域時增加自定義區域的檢測
      function TskForm.HitTest(P: TPoint):integer;
      begin
          ... ... (代碼略)  
          ///
          ///  標題工具區域
          ///    需要前面扣除窗體圖標區域
          if (Result = HTNOWHERE) and (FToolbar.Visible) then
          begin
            r.Left := rCaptionRect.Left + 2 + GetSystemMetrics(SM_CXSMICON) + SPALCE_CAPTIONAREA;
            R.Top := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2;
            R.Right := R.Left + FToolbar.Border.Width;
            R.Bottom := R.Top + FToolbar.Border.Height;
      
            if FToolbar.FOffset.X = -1 then
              FToolbar.FOffset := r.TopLeft;
      
            if PtInRect(r, p) then
              Result := HTCAPTIONTOOLBAR;
          end;
        end;
      end;
      

      標題工具條實現

      1、準備繪制的區域
      2、確定繪制區域大小
      3、實現繪制
      4、響應消息

      確定繪制區域大小

      考慮到按鈕是動態增加上去,需要根據實際標題區域的按鈕數量來確定實際大小。所有的Action存放在記錄中,這樣每次只要循環Action數組就可以獲得相應寬度。

      區域的寬度包括:兩條分割線 + 下拉配置菜單 + Button * Count

      /// 用于保存Action的信息
      TcpToolButton = record
        Action: TBasicAction;
        Enabled: boolean;
        Visible: Boolean;
        ImageIndex: Integer;        // 考慮到標題功能圖標和實際工具欄功能使用不同圖標情況,分開圖標索引
        Width: Word;                // 實際占用寬度,考慮后續加不同的按鈕樣式使用
        Fade: Word;                 // 褪色量 0 - 255
        SaveEvent: TNotifyEvent;    // 原始的Action OnChange事件
      end;
      
      ///
      /// 計算實際占用尺寸
      function TcpToolbar.CalcSize: TRect;
      const
        SIZE_SPLITER = 10;
        SIZE_POPMENU = 10;
        SIZE_BUTTON  = 20;
      var
        w, h: Integer;
        I: Integer;
      begin
        ///
        ///  占用寬度
        ///     如果考慮比較復雜的按鈕樣式和顯示標題等功能,那么需要計算每個按鈕實際占用寬度才能獲得。
        w := SIZE_SPLITER * 2 + SIZE_POPMENU;
        for I := 0 to FCount - 1 do
          w := w + FItems[i].Width;
        h := SIZE_BUTTON;
        Result := Rect(0, 0, w, h);
      end;
      

      占用區域大小的問題解決,繪制問題主要考慮在什么位置繪制,怎么獲得Action的圖標和實際的狀態。以正常情況考慮繪制區域:從原點(0,0)開始繪制,這樣比較符合一般的習慣。只要在繪制前對畫布重新設置原點,就能實現。

      /// 繪制工具條
      if FToolbar.Visible and (rCaptionRect.Right > rCaptionRect.Left) then
      begin
        /// 防止出現繪制出多余區域,當區域不夠時需要進行剪切。
        ///  如: 窗體縮小時
        CurrentIdx := 0;
        bClipRegion := rCaptionRect.Width < FToolbar.Border.Width;
        if bClipRegion then
        begin
          ClipRegion := CreateRectRgnIndirect(rCaptionRect);
          CurrentIdx := SelectClipRgn(DC, ClipRegion);
          DeleteObject(ClipRegion);
        end;
      
        /// 設置原點偏移量
        iLeftOff := rCaptionRect.Left;
        iTopOff := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2;
        MoveWindowOrg(DC, iLeftOff, iTopOff);
        FToolbar.Paint(DC);
        MoveWindowOrg(DC, -iLeftOff, -iTopOff);
      
        if bClipRegion then
          SelectClipRgn(DC, CurrentIdx);
      
        /// 扣除工具條區域
        rCaptionRect.Left := rCaptionRect.Left + FToolbar.Border.Width + SPALCE_CAPTIONAREA;
      end;
      

      獲取Action的圖標

      直接從ImageList中獲取??紤]標題區域是純色,能讓標題工具條顯的更美觀(個人審美),能讓工具條支持2中不同的圖標。畫了一組純白的圖標用于標題區域的顯示。

      // 創建Bmp,支持透明
      // cIcon := TBitmap.Create;
      // cIcon.PixelFormat := pf32bit;  // 支持透明
      // cIcon.alphaFormat := afIgnored;
      
      function TcpToolbar.LoadActionIcon(Idx: Integer; AImg: TBitmap):Boolean;
      var
        bHasImg: Boolean;
      begin
        /// 獲取Action的圖標
        AImg.Canvas.Brush.Color := clBlack;
        AImg.Canvas.FillRect(Rect(0,0, AImg.Width, AImg.Height));
        bHasImg := False;
        if (FImages <> nil) and (FItems[Idx].ImageIndex >= 0) then
          bHasImg := FImages.GetBitmap(FItems[Idx].ImageIndex, AImg);
        if not bHasImg and (FItems[Idx].Action is TCustomAction) then
          with TCustomAction(FItems[Idx].Action) do
            if (Images <> nil) and (ImageIndex >= 0) then
              bHasImg := Images.GetBitmap(ImageIndex, AImg);
        Result := bHasImg;
      end;
      

      繪制工具條

      有了尺寸和Action就可以直接進行繪制。鼠標滑過和按下狀態的處理方法和系統按鈕區域的方法一致。

      procedure TcpToolbar.Paint(DC: HDC);
      
        function GetActionState(Idx: Integer): TSkinIndicator;
        begin
          Result := siInactive;
          if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then
            Result := siPressed
          else if Idx = FHotIndex then
            Result := siHover;
        end;
      
      var
        cIcon: TBitmap;
        r: TRect;
        I: Integer;
        iOpacity: byte;
      begin
        ///
        ///  工具條繪制
        ///
      
        /// 分割線
        r := Border;
        r.Right := r.Left + RES_CAPTIONTOOLBAR.w;
        SkinData.DrawElement(DC, steSplitter, r);
        OffsetRect(r, r.Right - r.Left, 0);
      
        /// 繪制Button
        cIcon := TBitmap.Create;
        cIcon.PixelFormat := pf32bit;
        cIcon.alphaFormat := afIgnored;
        for I := 0 to FCount - 1 do
        begin
          r.Right := r.Left + FItems[i].Width;
          if FItems[I].Enabled then
            SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade);
          if LoadActionIcon(i, cIcon) then
          begin
            iOpacity := 255;
            /// 處理不可用狀態,圖標顏色變暗。
            ///   簡易處理,增加繪制透明度。
            if not FItems[i].Enabled then
              iOpacity := 100;
      
            SkinData.DrawIcon(DC, r, cIcon, iOpacity);
          end;
          OffsetRect(r, r.Right - r.Left, 0);
        end;
        cIcon.free;
      
        /// 分割條
        r.Right := r.Left + RES_CAPTIONTOOLBAR.w;
        SkinData.DrawElement(DC, steSplitter, r);
        OffsetRect(r, r.Right - r.Left, 0);
      
        /// 繪制下拉菜單按鈕
        r.Right := r.Left + RES_CAPTIONTOOLBAR.w;
        SkinData.DrawElement(DC, stePopdown, r);
      end;
      

      響應鼠標事件

      對于一個工具條,需要相應的事件有三個鼠標滑過、按下彈起。滑過是出現Hot效果,按下時處理Button被壓下的效果,彈起時執行實際的Action事件。簡單處理處理的這三種效果,如果考慮動畫效果。那么需要創建一個時鐘,設置個背景褪色量(其實是個Alpha透明通道值),然后根據褪色量在時鐘消息中進行繪制。時鐘最好設置在主皮膚類(TskForm)上,不必為每個區域創建一個句柄,這樣可以減少系統資源(句柄)的占用。

      統一消息入口,如果處理了此消息就返回True。這樣可以讓外部知道是否此消息被處理,以便外部作進一步的響應處理。

      function TFormCaptionPlugin.HandleMessage(var Message: TMessage): Boolean;
      begin
        Result := True;
      
        case Message.Msg of
          WM_NCMOUSEMOVE    : MouseMove(ScreenToClient(TWMNCMouseMove(Message).XCursor, TWMNCMouseMove(Message).YCursor));
          WM_NCLBUTTONDOWN  : MouseDown(mbLeft, ScreenToClient(TWMNCLButtonDown(Message).XCursor, TWMNCLButtonDown(Message).YCursor));
          WM_NCHITTEST      : HitWindowTest(ScreenToClient(TWMNCHitTest(Message).XPos, TWMNCHitTest(Message).YPos));
          WM_NCLBUTTONUP    : MouseUp(mbLeft, ScreenToClient(TWMNCLButtonUp(Message).XCursor, TWMNCLButtonUp(Message).YCursor));
      
          else
            Result := False;
        end;
      end;
      

      這里一個比較關鍵的是,鼠標在這個區域內的實際位置。一般窗體都會有Handle,所以能直接通過API轉換鼠標位置。

      區域需要依靠主窗口的位置才能獲得。每次窗口在處理尺寸時,區域的偏移位置是可以獲得的。像標題工具條這種左靠齊,其實這個偏移位置算好后就肯定是不會變的。

      // 偏移量 = 有效標題區域 - 系統圖標位置 - 區域間隙
      r.Left := rCaptionRect.Left + 2 + GetSystemMetrics(SM_CXSMICON) + SPALCE_CAPTIONAREA;
      r.Top := rCaptionRect.Top + (rCaptionRect.Height - FToolbar.Border.Height) div 2;
      
      function TFormCaptionPlugin.ScreenToClient(x, y: Integer): TPoint;
      var
        P: TPoint;
      begin
        /// 調整位置 
        ///    以 FOffset 為中心位置
        P := FOwner.NormalizePoint(Point(x, Y));
        p.X := p.X - FOffset.X;
        p.Y := p.y - FOffset.Y;
        Result := p;
      end;
      

      上面鼠標的消息最終通過HitTest獲取,實際鼠標所在按鈕位置。這個處理方法和外部的TskForm處理方法一致,檢測位置設置狀態參數然后再重繪。

      如:鼠標滑過時的消息處理。

      procedure TcpToolbar.MouseMove(p: TPoint);
      var
        iIdx: Integer;
      begin
        /// 鼠標滑入時設置HotIndex值
        iIdx := HitTest(p);
        if iIdx <> FHotIndex then
        begin
          FHotIndex := iIdx;
          Invalidate;
        end;
      end;
      

      坐標所在按鈕區域檢測 HitTest

      function TcpToolbar.HitTest(P: TPoint): integer;
      var
        iOff: Integer;
        iIdx: integer;
        I: Integer;
      begin
        ///
        ///  檢測鼠標位置
        ///    鼠標位置的 FCount位 為工具條系統菜單位置。
        iIdx := -1;
        iOff := RES_CAPTIONTOOLBAR.w;
        if p.x > iOff then
        begin
          for I := 0 to FCount - 1 do
          begin
            if p.X < iOff then
              Break;
      
            iIdx := i;
            inc(iOff, FItems[i].Width);
          end;
      
          if p.x > iOff then
          begin
            iIdx := -1;
            inc(iOff, RES_CAPTIONTOOLBAR.w);
            if p.x > iOff then
              iIdx := FCount;  // FCount 為系統菜單按鈕
          end;
        end;
      
        Result := iIdx;
      end;
      

      還有些細節方面的處理,如鼠標離開這個區域時的處理。這樣整個工具區的基本處理完成,整個工具條區域的處理還是相對比較簡單。

      Action狀態處理

      Action處理主要是考慮,當外部改變Action狀態。如:無效,不可見的一些事件處理。標準的處理方法是在關聯Action是創建一個ActionLink實現聯動,由于TskForm沒有從TControl繼承,沒法使用此方法進行處理。在TBasicAction改變狀態時會觸發一個OnChange的保護(protected)事件,可以直接把事件掛接上去,就能簡單處理狀態。

      技巧:保護方法的訪問,創建一個訪問類,進行引用。下面寫法就應使用父類的保護方法。
      TacWinControl = class(TWinControl);
      TacAction = class(TBasicAction);

      ZeroMemory(@FItems[FCount], SizeOf(TcpToolButton));
        FItems[FCount].Action := Action;
        FItems[FCount].Enabled := true;       // <--- 這里應該獲取Actoin的當前狀態,這里簡略處理。
        FItems[FCount].Visible := True;       // <--- 同上,注:現有代碼中并未處理此狀態
        FItems[FCount].ImageIndex := AImageIndex;
        FItems[FCount].Width := 20;
        FItems[FCount].Fade  := 255;
        FItems[FCount].SaveEvent := TacAction(Action).OnChange;  // 保存原事件
        TacAction(Action).OnChange := DoOnActionChange;          // 掛接事件
      

      注意:不要把原事件丟了,需要保存。防止外部有掛接的情況下出現原事件無法執行。

      根據狀態的不同,直接修改記錄的Enabled 和 Visible 這兩個狀態。繪制時可以直接使用。

      procedure TcpToolbar.DoOnActionChange(Sender: TObject);
      var
        idx: Integer;
        bResize: Boolean;
      begin
        if Sender is TBasicAction then
        begin
          idx := IndexOf(TBasicAction(Sender));
          if (idx >= 0) and (idx < FCount) then
          begin
            ///  外部狀態改變響應
            if FItems[idx].Action.InheritsFrom(TContainedAction) then
            begin
              FItems[idx].Enabled := TContainedAction(Sender).Enabled;
              bResize := FItems[idx].Visible <> TContainedAction(Sender).Visible;
              if bResize then
              begin
                FItems[idx].Visible := not FItems[idx].Visible;
                Update
              end
              else
                Invalidate;
            end;
      
            /// 執行原有事件
            if Assigned(FItems[idx].SaveEvent) then
              FItems[idx].SaveEvent(Sender);
          end;
        end;
      end;
      

      在繪制時就可以通過記錄中的狀態和鼠標位置狀態進行判斷,來繪制出所需要的效果

      ... ...
        // 如果按鈕有效,那么進行按鈕底色繪制。
        if FItems[I].Enabled then
          SkinData.DrawButtonBackground(DC, GetActionState(i), r, FItems[i].Fade);
        if LoadActionIcon(i, cIcon) then
        begin
          /// 處理不可用狀態,圖標顏色變暗。
          ///   簡易處理,增加繪制透明度。
          iOpacity := 255;
          if not FItems[i].Enabled then
            iOpacity := 100;
      
          SkinData.DrawIcon(DC, r, cIcon, iOpacity);
        end;
        ... ...
      
        // 獲取Action底色的顯示狀態
        //  按下狀態、滑過狀態、默認狀態
        function GetActionState(Idx: Integer): TSkinIndicator;
        begin
          Result := siInactive;
          if (Idx = FPressedIndex) and (FHotIndex = FPressedIndex) then
            Result := siPressed
          else if Idx = FHotIndex then
            Result := siHover;
        end;
      

      在窗體上加入測試Action

      procedure TForm11.FormCreate(Sender: TObject);
      begin
        FTest.Toolbar.Images := ImageList2;
        FTest.Toolbar.Add(Action1, 0);
        FTest.Toolbar.Add(Action2, 1);
        FTest.Toolbar.Add(Action3, 2);
      end;
      

      完成~~

      最終效果,就是上面的GIF效果。想做的更好,那么就需要在細節上考慮。細節是最花時間的地方。

      相關功能實現:

      其實這個功能在Win7下已經有此接口可以實現(很久以前用過具體名字忘記了,沒寫日志的后果-_-),系統自帶的畫圖就是使用此接口實現的。但有個問題就是XP下木有此功能。感興趣的可以Google一下。

      相關API:

      • MoveWindowOrg ---- 設置繪制原點
      • CreateRectRgnIndirect ---- 創建區域
      • SelectClipRgn ---- 剪切繪制區域

      開發環境:

      • XE3
      • Win7

      完整源代碼:

      亚洲成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>