首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

MFC 多模式視窗介面實作:正常/簡潔/精簡三種顯示模式動態切換

     編輯:VC++

專案靈感:RealPlayer 的多模式介面

RealPlayer 提供「正常」、「簡潔」、「精簡」三種視窗模式,使用者可依需求切換。本文實作同樣功能:動態計算視窗尺寸、顯示/隱藏選單列、工具列、狀態列、檢視區,實現無縫模式切換。

正常模式
簡潔模式
精簡模式

核心幾何計算邏輯

關鍵在於取得各 UI 元素的螢幕座標矩形,並依模式重新計算視窗高度 (寬度保持不變):

模式可見元素高度計算公式
正常 (0)選單列、工具列、檢視區、狀態列原始視窗高度
簡潔 (1)選單列、工具列、檢視區 (隱藏狀態列)視窗高度 - 客戶區高度 + 工具列高度 - 邊框高度
精簡 (2)工具列 (隱藏選單列、狀態列、檢視區)標題列高度 + 工具列高度 + 邊框高度 × 2

完整實作:CMainFrame::ViewMode


void CMainFrame::ViewMode(int nViewType)
{
    // 1. 取得各元素矩形 (螢幕座標)
    CRect rectWindow, rectClient, rectToolbar, rectStatusbar;
    GetWindowRect(&rectWindow);           // 整個視窗
    GetClientRect(&rectClient);           // 客戶區 (不含標題列、選單、邊框)
    m_wndToolBar.GetWindowRect(&rectToolbar); // 工具列
    
    // 狀態列需透過 ID 取得指標
    CStatusBar* pStatus = (CStatusBar*)AfxGetApp()->m_pMainWnd->GetDescendantWindow(AFX_IDW_STATUS_BAR);
    if (pStatus) pStatus->GetWindowRect(&rectStatusbar);
    
    // 系統度量
    int cxBorder = GetSystemMetrics(SM_CXBORDER);
    int cyBorder = GetSystemMetrics(SM_CYBORDER);
    int cyCaption = GetSystemMetrics(SM_CYCAPTION);
    int cyMenu = GetSystemMetrics(SM_CYMENU); // 選單列高度
    
    // 確保工具列停靠狀態
    DockControlBar(&m_wndToolBar);
    
    int newWidth = rectWindow.Width();  // 寬度不變
    int newHeight = rectWindow.Height(); // 預設
    HMENU hOldMenu = NULL;              // 用於精簡模式保存選單
    
    switch (nViewType)
    {
        case 0: // 正常模式
        {
            // 還原選單
            if (m_hSavedMenu) ::SetMenu(m_hWnd, m_hSavedMenu);
            
            // 顯示狀態列
            if (pStatus) pStatus->ShowWindow(SW_SHOW);
            
            // 還原工具列可見性 (若之前隱藏)
            m_wndToolBar.ShowWindow(SW_SHOW);
            
            newHeight = rectWindow.Height(); // 原始高度
            break;
        }
        
        case 1: // 簡潔模式:隱藏狀態列
        {
            // 確保選單存在
            if (m_hSavedMenu) ::SetMenu(m_hWnd, m_hSavedMenu);
            
            // 隱藏狀態列
            if (pStatus) pStatus->ShowWindow(SW_HIDE);
            
            // 高度 = 視窗高 - 客戶區高 + 工具列高 - 邊框
            // 邏輯:移除檢視區與狀態列,保留標題列+選單列+工具列+邊框
            newHeight = rectWindow.Height() - rectClient.Height() + rectToolbar.Height() - cyBorder;
            break;
        }
        
        case 2: // 精簡模式:僅工具列
        {
            // 保存並移除選單
            if (!m_hSavedMenu) m_hSavedMenu = ::GetMenu(m_hWnd);
            ::SetMenu(m_hWnd, NULL);
            
            // 隱藏狀態列與工具列 (工具列稍後在計算高度後顯示)
            if (pStatus) pStatus->ShowWindow(SW_HIDE);
            m_wndToolBar.ShowWindow(SW_HIDE); // 先隱藏避免閃爍
            
            // 高度 = 標題列 + 工具列 + 上下邊框
            newHeight = cyCaption + rectToolbar.Height() + cyBorder * 2;
            
            // 重新顯示工具列 (位於客戶區頂端)
            m_wndToolBar.ShowWindow(SW_SHOW);
            RecalcLayout(); // 重新佈局
            break;
        }
    }
    
    // 2. 套用新尺寸 (保持左上角位置)
    SetWindowPos(&wndTop, rectWindow.left, rectWindow.top, newWidth, newHeight, SWP_SHOWWINDOW | SWP_NOZORDER);
}

關鍵成員變數宣告


class CMainFrame : public CFrameWnd
{
    // ...
protected:
    HMENU m_hSavedMenu = NULL; // 精簡模式時保存的選單控制代碼
    // ...
};

選單命令對應


// MainFrm.cpp 訊息對映
ON_COMMAND(ID_VIEW_NORMAL, &CMainFrame::OnViewNormal)
ON_COMMAND(ID_VIEW_COMPACT, &CMainFrame::OnViewCompact)
ON_COMMAND(ID_VIEW_MINIMAL, &CMainFrame::OnViewMinimal)
ON_UPDATE_COMMAND_UI(ID_VIEW_NORMAL, &CMainFrame::OnUpdateViewMode)
ON_UPDATE_COMMAND_UI(ID_VIEW_COMPACT, &CMainFrame::OnUpdateViewMode)
ON_UPDATE_COMMAND_UI(ID_VIEW_MINIMAL, &CMainFrame::OnUpdateViewMode)

void CMainFrame::OnViewNormal()  { ViewMode(0); }
void CMainFrame::OnViewCompact() { ViewMode(1); }
void CMainFrame::OnViewMinimal() { ViewMode(2); }

void CMainFrame::OnUpdateViewMode(CCmdUI* pCmdUI)
{
    // 單選狀態同步 (需成員變數 m_nCurrentMode 記錄)
    pCmdUI->SetRadio(pCmdUI->m_nID == ID_VIEW_NORMAL + m_nCurrentMode);
}

進階優化:無縫動畫切換


void CMainFrame::AnimateToHeight(int targetHeight, int durationMs = 300)
{
    CRect rect; GetWindowRect(&rect);
    int startHeight = rect.Height();
    int steps = 20;
    int stepMs = durationMs / steps;
    int delta = (targetHeight - startHeight) / steps;
    
    for (int i = 1; i <= steps; ++i)
    {
        int h = startHeight + delta * i;
        SetWindowPos(NULL, rect.left, rect.top, rect.Width(), h, SWP_NOZORDER | SWP_NOMOVE);
        Sleep(stepMs); // 或使用 Timer 避免阻塞 UI 執行緒
    }
    // 確保精確最終值
    SetWindowPos(NULL, rect.left, rect.top, rect.Width(), targetHeight, SWP_NOZORDER | SWP_NOMOVE);
}

// ViewMode 中呼叫
// SetWindowPos(...) → 改為 AnimateToHeight(newHeight);

現代框架對應實作

框架對應機制關鍵 API
WPFVisualStateManager + Grid.RowDefinitions Height=Auto/*VisualStateManager.GoToState, GridLength
WinUI 3VisualState + XAML 控制項 VisibilityVisualStateManager, UIElement.Visibility
WinFormsTableLayoutPanel + Control.Visible/DockControl.Visible, Form.ClientSize
QtQStackedWidget / QWidget::setVisible + LayoutQWidget::setVisible, QLayout
Web (CSS)CSS Grid/Flex + Class 切換display: none, grid-template-rows

常見問題與解決方案

⚠ 1. 選單移除後無法恢復

必須在移除前 m_hSavedMenu = ::GetMenu(m_hWnd) 保存,恢復時 ::SetMenu(m_hWnd, m_hSavedMenu)。若直接傳 NULL 給 SetMenu 且未保存,選單永久遺失。

⚠ 2. 工具列停靠狀態混亂

模式切換前呼叫 DockControlBar(&m_wndToolBar) 確保停靠;切換後呼叫 RecalcLayout() 重新計算佈局。

⚠ 3. 狀態列指標取得失敗

使用 AfxGetApp()->m_pMainWnd->GetDescendantWindow(AFX_IDW_STATUS_BAR) 而非直接存取成員,因 MDI/SDI 架構下狀態列可能屬於框架視窗而非主視窗。

⚠ 4. 高度計算在高 DPI 下異常

啟用 Per-Monitor DPI Awareness (Manifest 設定),所有 GetSystemMetrics、GetWindowRect 均回傳邏輯像素,計算一致。若混用實體像素需手動縮放。

結語

多模式視窗介面的核心在於幾何計算精確與UI 元素狀態同步。透過 GetWindowRect/GetClientRect/GetSystemMetrics 取得基準尺寸,依模式組合公式計算新高度,配合選單保存/恢復、工具列停靠/重佈局、狀態列顯示/隱藏,即可實現 RealPlayer 風格的動態介面切換。此技術對於資訊密度調適、觸控裝置適配、無障礙縮放等現代應用場景同樣適用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved