CMFCPropertySheet "Page" 资源未使用动态布局调整大小

CMFCPropertySheet "Page" resources are not resizing with dynamic layout

我真的很困惑。 :(

这是一个新的 属性 sheet:

#include "stdafx.h"
#include "resource.h"
#include "VisitsRotaMFCPropertySheet.h"


CVisitsRotaMFCPropertySheet::CVisitsRotaMFCPropertySheet()
    :CResizingMFCPropertySheet(_T("VisitsRota"), AFX_IDS_APP_TITLE, nullptr, 0)
{
    ConstructSheet();
}

CVisitsRotaMFCPropertySheet::~CVisitsRotaMFCPropertySheet()
{
}


BOOL CVisitsRotaMFCPropertySheet::OnInitDialog()
{
    BOOL bResult = CResizingMFCPropertySheet::OnInitDialog();

    m_Menu.LoadMenu(IDR_MENU);
    SetMenu(&m_Menu);

    return bResult;
}


void CVisitsRotaMFCPropertySheet::ConstructSheet()
{
    m_psh.dwFlags |= PSH_NOAPPLYNOW;

    AddPage(&m_ElderlyInfirmPage);
    AddPage(&m_ShepherdingPage);
}

它源自CResizingMFCPropertySheet。这是 class:

的来源

https://www.dropbox.com/s/fzpfo4c3dpt6l51/ResizingMFCPropertySheet.cpp?dl=0

现在,我在这个 window 中有两个页面。这是一个定义:

IDD_PAGE_ELDERLY_INFIRM DIALOGEX 0, 0, 420, 202
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CAPTION
CAPTION "Elderly && Infirm"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    GROUPBOX        "Elders ...",IDC_STATIC,6,7,132,188
    LISTBOX         IDC_LIST_BOOKSTUDY,12,18,120,147,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
    PUSHBUTTON      "Add",IDC_BUTTON_ADD_GROUP,12,172,35,18
    PUSHBUTTON      "Edit",IDC_BUTTON_EDIT_ELDER,55,172,35,18
    PUSHBUTTON      "Delete",IDC_BUTTON_DELETE_GROUP,97,172,35,18
    GROUPBOX        "Publishers ...",IDC_STATIC,144,7,132,188
    LISTBOX         IDC_LIST_ELDERY_INFIRM,150,18,120,147,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
    PUSHBUTTON      "Add",IDC_BUTTON_ADD_ELDERLY,150,172,35,18
    PUSHBUTTON      "Edit",IDC_BUTTON_EDIT_ELDERLY,193,172,35,18
    PUSHBUTTON      "Delete",IDC_BUTTON_DELETE_ELDERLY,235,172,35,18
    GROUPBOX        "Report Settings ...",IDC_STATIC,281,7,132,188
    LTEXT           "Starting month:",IDC_STATIC,286,18,120,8
    COMBOBOX        IDC_COMBO_MONTH,286,31,120,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    LTEXT           "Number of months:",IDC_STATIC,286,49,78,12
    COMBOBOX        IDC_COMBO_NUM_MONTHS,376,49,30,96,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    LTEXT           "Number of publishers to visit each month:",IDC_STATIC_NUM_PUB,286,65,84,18
    COMBOBOX        IDC_COMBO_PUB_PER_MONTH,376,66,30,12,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
    LTEXT           "Starting publisher:",IDC_STATIC,286,90,120,8
    COMBOBOX        IDC_COMBO_PUBLISHER,286,103,120,12,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
END

它已正确设置为页面,我最初通过 IDE:

设置了控制数据
IDD_PAGE_ELDERLY_INFIRM AFX_DIALOG_LAYOUT
BEGIN
    0,
    0, 0, 0, 100,
    0, 0, 0, 100,
    0, 100, 0, 0,
    0, 100, 0, 0,
    0, 100, 0, 0,
    0, 0, 0, 100,
    0, 0, 0, 100,
    0, 100, 0, 0,
    0, 100, 0, 0,
    0, 100, 0, 0,
    100, 0, 0, 100,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0,
    100, 0, 0, 0
END

我已调整我的 CDialog 应用程序以调用 属性 sheet。 sheet 本身尺寸:

为什么 sheet 控件不能自动调整大小?我只是不明白。我的其他应用程序使用相同的基础 class,但所有这些 属性 页面使用动态布局功能正确调整控件大小等。

更新

我将此添加到我的一个页面中:

void CElderlyInfirmPage::OnSize(UINT nType, int cx, int cy)
{
    CMFCPropertyPage::OnSize(nType, cx, cy);

    AfxMessageBox(_T("Size"));

    // TODO: Add your message handler code here
    auto pManager = GetDynamicLayout();
    if (pManager != nullptr)
    {
        AfxMessageBox(_T("Valid"));
    }
}

确认“页面”实际上没有动态布局管理器。只有 sheet 可以。所以我认为问题在于我们不能使用动态布局机制。

更新 2

我取得了一些进步。示例:

事实证明,属性 页面似乎没有像对话框那样加载动态布局资源。我开始手动创建它:

BOOL CElderlyInfirmPage::OnInitDialog()
{
    CMFCPropertyPage::OnInitDialog();

    // TODO:  Add extra initialization here
    ReadSettings();
    InitMonthCombo();

    // Init to THIS month
    COleDateTime    datNow = COleDateTime::GetCurrentTime();
    m_cbMonth.SetCurSel(datNow.GetMonth()-1);

    EnableDynamicLayout(TRUE);
    auto pManager = GetDynamicLayout();
    if (pManager != nullptr)
    {
        pManager->Create(this);
        pManager->AddItem(IDC_COMBO_MONTH, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
        pManager->AddItem(IDC_COMBO_NUM_MONTHS, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
        pManager->AddItem(IDC_COMBO_PUB_PER_MONTH, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
        pManager->AddItem(IDC_COMBO_PUBLISHER, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
    }

    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

如您所见,控件现在移动了,所以这是一个进步。但是现在的问题是我在这些页面上有很多IDC_STATIC控件,我不想更改ID号。这是因为应用程序已经有本地化翻译,如果我更改 ID 值,我会破坏翻译。所以我想知道我是否可以使用 [CMFCDynamicLayout::LoadResource][3] 方法从 RC 文件中加载完整的设置。但是我不知道如何在这里调用 LoadResource 。我相信这将是这个问题的答案。

更新 3

我刚刚跟踪代码,如果你看这里:

LRESULT CPropertySheet::HandleInitDialog(WPARAM, LPARAM)
{
    LRESULT lResult = OnInitDialog();

    CMFCDynamicLayout* pDynamicLayout = GetDynamicLayout();
    if (pDynamicLayout != NULL)
    {
        CRect rectWindow;
        GetWindowRect(rectWindow);
        m_sizeMin = rectWindow.Size();

        for (CWnd *pChild = GetWindow(GW_CHILD); pChild->GetSafeHwnd() != NULL; pChild = pChild->GetWindow(GW_HWNDNEXT))
        {
            HWND hwndChild = pChild->GetSafeHwnd();
            if (!pDynamicLayout->HasItem(hwndChild))
            {
                if (pChild->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
                {
                    pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
                }
                else if (IsLeftNavigationPane(hwndChild))
                {
                    pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeVertical(100));
                }
                else if (DYNAMIC_DOWNCAST(CPropertyPage, pChild) == NULL || CanAddPageToDynamicLayout())
                {
                    pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
                }
            }
        }
    }

    return lResult;
}

它似乎并没有真正正确地使用布局。

我尝试使用:

LoadDynamicLayoutResource(m_lpszTemplateName);

然后我追踪到了它。它最终在这里结束:

BOOL CMFCDynamicLayout::LoadResource(CWnd* pHostWnd, LPVOID lpResource, DWORD dwSize)
{
    if (pHostWnd->GetSafeHwnd() == NULL || !::IsWindow(pHostWnd->GetSafeHwnd()) || lpResource == NULL)
    {
        return FALSE;
    }

    CMFCDynamicLayoutData layoutData;
    BOOL bResult = layoutData.ReadResource(lpResource, (UINT)dwSize);
    layoutData.ApplyLayoutDataTo(pHostWnd, FALSE);

    return bResult;
}

第一个 if 语句的 ApplyLayoutDataTo 调用失败:

BOOL CMFCDynamicLayoutData::ApplyLayoutDataTo(CWnd* pHostWnd, BOOL bUpdate)
{
    if (pHostWnd->GetSafeHwnd() == NULL || m_listCtrls.IsEmpty())
    {
        return FALSE;
    }

    ASSERT_VALID(pHostWnd);

    pHostWnd->EnableDynamicLayout(FALSE);
    pHostWnd->EnableDynamicLayout();

m_listCtrls.IsEmpty() 是空的。所以无论如何它都没有正确读取它。

我想我别无选择,只能为我所有的控件分配 ID,甚至是静态控件,然后手动构建动态布局。除非你有其他想法。

所有从 CDialog 派生的 类 都已启用动态布局,调用默认 CDialog::OnInitDialog,后者又使用 CMFCDynamicLayout::LoadResource 读取子项的调整大小信息控件。

其中包括 CMFCPropertyPage。信息已经加载,所以如果您调用 EnableDynamicLayout 它会删除现有对象并创建一个新对象。只需删除对 EnableDynamicLayout 的调用。

这样 pManager->Create(this); 就没有必要了,但你可以把它放在那里。它不会执行任何操作,因为 pManager 已经创建并且该方法知道不会创建两次。

CPropertySheet 确实需要 EnableDynamicLayoutpManager->Create。 PropertySheet 不能在对话框编辑器中设计,因此 MFC 忽略对其子 windows 的大小调整。必须手动实现动态调整大小。

MCVE:

class CMyPage : public CMFCPropertyPage
{
    CButton bn;
    BOOL OnInitDialog()
    {
        CMFCPropertyPage::OnInitDialog();

        //add test button dynamically
        bn.Create(L"Test", WS_CHILD | WS_VISIBLE, CRect(0, 0, 100, 30), this, 301);

        auto pManager = GetDynamicLayout();
        if(pManager != nullptr)
        {
            pManager->AddItem(bn.GetDlgCtrlID(),
              CMFCDynamicLayout::MoveHorizontal(100),
              CMFCDynamicLayout::SizeNone());
        }
        return TRUE;
    }
};

class CMySheet :public CMFCPropertySheet
{
public:
    CMyPage Page1;
    CMySheet()
    {
        Page1.Construct(IDD_PAGE1);
        AddPage(&Page1);
    }

    static int CALLBACK XmnPropSheetCallback(HWND hWnd, UINT message, LPARAM lParam)
    {
        extern int CALLBACK AfxPropSheetCallback(HWND, UINT message, LPARAM lParam);
        // XMN: Call MFC's callback
        int nRes = AfxPropSheetCallback(hWnd, message, lParam);
        if (message == PSCB_PRECREATE)
            ((LPDLGTEMPLATE)lParam)->style |= (DS_3DLOOK | DS_SETFONT
                | WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_CAPTION);
        return nRes;
    }

    BOOL OnInitDialog()
    {
        BOOL res = CMFCPropertySheet::OnInitDialog();
        EnableDynamicLayout(TRUE);//required for propertysheet
        auto pManager = GetDynamicLayout();
        if(pManager)
        {
            pManager->Create(this);
            for(CWnd *child = GetWindow(GW_CHILD); 
              child; child = child->GetWindow(GW_HWNDNEXT))
            {
                if(child->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
                    pManager->AddItem(*child, 
                      CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100),
                      CMFCDynamicLayout::SizeNone());
                else
                    pManager->AddItem(*child, 
                      CMFCDynamicLayout::MoveNone(),
                      CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
            }
        }

        return res;
    }

    INT_PTR DoModal()
    {
        // Hook into property sheet creation code
        m_psh.dwFlags |= PSH_USECALLBACK;
        m_psh.pfnCallback = XmnPropSheetCallback;
        return CMFCPropertySheet::DoModal();
    }
};

...
CMySheet sh;
sh.DoModal();