MFC MDI 收集 "apply" 按钮例程的控制状态

MFC MDI Collecting control states for the "apply" button routine

在我的应用程序的其他一些线程中提到我的代码不正确,因为应用按钮存在。我现在明白了。据说收集控件然后点击应用发送数据。

我现在知道如何处理了。所以为了通识教育。我有 1 property sheet 和 5 property pages。只是为了使用中的一般控件。在所有 5 个中,只有 radio 控件和 buttons,没有 edit controls(还)。

假设每个页面上有 2 radios buttons1 button.. 其中 page 1radio1radio2button 1page 2radio3radio4button2...等等。

我知道当用户选择按钮或收音机之类的东西时,需要调用 IsModified(TRUE) 以使应用按钮从灰色变为活动状态。

扫描所有控件然后应用它们的代码是什么样的?我从来没有做过,而且我似乎找不到一个不是已经超级忙的例子来了解如何做。

任何人都有教程或代码片段或从我上面描述的控件构建入门,可以演示如何执行此操作?

更新:

好的,我添加了 DDX 变量:

void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
    CMFCPropertyPage::DoDataExchange(pDX);
    DDX_Control(pDX, STYLE_3D_USER, m_style_3d);
    DDX_Control(pDX, STYLE_FLAT_USER, m_style_flat);
    DDX_Control(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_shared_h_scroll);
    DDX_Control(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
    DDX_Control(pDX, STYLE_3D_ONENOTE_USER, m_style_onenote);
    DDX_Control(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
    DDX_Control(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
    DDX_Control(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
}

我的电台选择如下:

void CSettingsUserTabs::OnBnClicked3dUser()
{
    //AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_3D_USER);
    UpdateData(TRUE);
}


void CSettingsUserTabs::OnBnClickedFlatUser()
{
    // TODO: Add your control notification handler code here
    //AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_FLAT_USER);
    UpdateData(TRUE);
}

..... and the rest of them....

所以对我来说,当我单击任何 radio button 时,我希望它扫描那个 属性 页面上的所有控件?如果是这样,所有变量都有值..我是否调用 IsModified(); 来启用 apply 按钮...然后执行所有无线电值,即只选择一个?这就是流量吗?

更新 2:

这就是我的模态对话框代码 MainFrame.cpp:

void CMainFrame::OnSettingsTools()
{
    SettingsSheet SettingsSheet(L"Application Settings");

    CSettingsPowerUser      pgePowerUser;   
    CSettingsToolbars       pgeToolbars;
    CSettingsTheme          pgeTheme;
    CSettingsUserTabs       pgeUserTabs;
    CSettingsReset          pgeReset;    
    
    SettingsSheet.AddPage(&pgeToolbars);
    SettingsSheet.AddPage(&pgeTheme);
    SettingsSheet.AddPage(&pgeUserTabs);
    SettingsSheet.AddPage(&pgePowerUser);
    SettingsSheet.AddPage(&pgeReset);

    INT_PTR nRet = -1;
    nRet = SettingsSheet.DoModal();

    // Handle the return value from DoModal
    switch (nRet)
    {
    case -1:
        AfxMessageBox(_T("Dialog box could not be created!"));
        break;
    case IDABORT:
        // Do something
        AfxMessageBox(_T("ABORT!"));
        break;
    case IDOK:
        // Do something
        OnUserTabStyles(1);
        AfxMessageBox(_T("OK!"));
        break;
    case IDCANCEL:
        // Do something
        AfxMessageBox(_T("CANCEL"));
        break;
    default:
        // Do something
        break;
    };    
}

验证 SettingsTabs.cpp 中的任何单选按钮是否更改的例程:

void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
    BOOL IsChecked = nCmdID;
    CheckRadioButton(STYLE_3D_USER, STYLE_3D_ROUNDED_SCROLL_USER, nCmdID);
    UpdateData(TRUE);
    m_tabCmdID = nCmdID;
    SetModified();
}

成员变量在SettingsUserTabs.cpp中的样子:

void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
    {
    CMFCPropertyPage::DoDataExchange(pDX);
    DDX_Radio(pDX, STYLE_3D_USER, m_style_3d);
    DDX_Radio(pDX, STYLE_FLAT_USER, m_style_flat);
    DDX_Radio(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_h_scroll);
    DDX_Radio(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
    DDX_Radio(pDX, STYLE_3D_ONENOTE_USER, m_style_3d_onenote);
    DDX_Radio(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
    DDX_Radio(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
    DDX_Radio(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
    }

构造函数在 SettingsUserTabs.cpp 中的样子:

CSettingsUserTabs::CSettingsUserTabs()
    : CMFCPropertyPage(IDD_SETTINGS_TABS)
    , m_style_3d(FALSE)
    , m_style_flat(FALSE)
    , m_style_flat_h_scroll(FALSE)
    , m_style_3d_scroll(FALSE)
    , m_style_3d_onenote(FALSE)
    , m_style_vs2005(FALSE)
    , m_style_3d_rounded(FALSE)
    , m_style_3d_rounded_scroll(FALSE)
    , m_tabCmdID(FALSE)
{
}

我现在看到的这个问题是当我尝试使用成员变量 m_tabCmdID 时它会返回到 unknown identifier 所以我不确定为什么成员变量不是见过。我希望像 OnUserTabStyles(m_tabCmdID); 那样使用它,以便它将所选按钮的参数传递给方法 OnUserTabStyles。现在我只是在里面放了一个 1 看看这个机制是否有效。我只是不清楚如何从 IDOK 访问 SettingsUserTabs.cpp 的成员变量。我错过了什么?

编辑:选项的 运行ge 在 resource.h 中按顺序排列为 200-207,这是我知道的,我知道很多人不喜欢 运行ge 选项,因为它们可能会损坏...这是我的代码,所以我不担心 运行ge 被弄乱。

更新 3:

好的,所以我终于在以下帮助下理解了康斯坦丁描述的机制:

https://helgeklein.com/blog/2009/10/radio-buttons-in-mfc-visual-studio-2008-c/

我的 Tab 键顺序不正确,也没有将组的第一个控件设置为 true。

有了这个,我现在在调试时得到按钮组中提到的值 0-7,因为我根据它在组中的位置从 0-7 单击每个单选按钮,即 8 个按钮。这是代码现在的样子。

SettingsUserTabs.cpp:

CSettingsUserTabs::CSettingsUserTabs()
    : CMFCPropertyPage(IDD_SETTINGS_TABS)
    , m_style_tabs(FALSE)
{
}

void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
    CMFCPropertyPage::DoDataExchange(pDX);
    DDX_Radio(pDX, STYLE_3D_USER, m_style_tabs);
}

void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
    UpdateData(TRUE);
    BOOL RadioValueSelected = m_style_tabs; // only here to see 0-7 value for debugging only, not needed, test only
    SetModified();
}

上面的mainframe.cpp(更新2):void CMainFrame::OnSettingsTools()还是老样子

所以这里是我的问题还不清楚的地方,我从mainframe.cpp中调用了domodal,成员变量m_style_tabsSettingsUserTabs.cpp中。当我尝试访问成员变量时,当我尝试做这样的事情时它说未知标识符 after domodal int temp = m_styles_tabs;。我在 mainframe.cpp

中有这个
void CMainFrame::DoDataExchange(CDataExchange* pDX)
{
    // TODO: Add your specialized code here and/or call the base class

    CMDIFrameWndEx::DoDataExchange(pDX);
} 

我希望成员会出现在 mainframe.cpp 中,所以我可以在 domodal 之后处理它,这就是我认为的重点?

如何访问成员变量以便对它执行 OnApply?我想我差不多明白了,我只是不清楚如何为实际应用本身执行最后几个步骤。

更新 4:

我询问重启的原因是当用户选择 tabs 属性 页面并选择 8 个选项中的 1 个时,该选项存储在注册表中并在 [=66] 期间读取=] 然后使用该样式。由于它是 OnCreate 我还没有找到“重绘”或使用新的 tab 设置的方法,除非重新启动应用程序。因此,通过执行 reg save > respawn > end old ...我希望对话框在同一选项卡 属性 页面重新打开,以便用户可以在选择 apply 时看到更改。 ..与一些对话框说“重新启动”......或其他什么。下面是用于演示它现在如何工作的代码。

OutputWnd.cpp

int COutputWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDockablePane::OnCreate(lpCreateStruct) == -1)
        return -1;

    CRect rectDummy;
    rectDummy.SetRectEmpty();

    // Create User Define tab style:
    int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
    // If the key doesn't exist, UserTableStyle will be 0 or FALSE;

    if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type

        int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.

        if (!m_wndTabs.Create(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle), rectDummy, this, 1))
        {
            TRACE0("Failed to create output tab window\n");
            return -1;      // fail to create
        }
    }
    else { // Default tabs style if Reg key does not exist i.e. new install/program reset
        
        if (!m_wndTabs.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, 1))
            {
                TRACE0("Failed to create output tab window\n");
                return -1;      // fail to create
            }
    }

… rest of function....

更新 5:

这是 SettingsUserTabs.cpp 中正在使用的 Apply:

BOOL CSettingsUserTabs::OnApply()
{
    // TODO: Add your specialized code here and/or call the base class

    AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry

    return CMFCPropertyPage::OnApply();
}

更新 6:

到目前为止一切正常,我 运行 遇到了一个障碍,试图应用在这里和这里学到的经验教训 link:

MFC MDI Substituting a class member dynamically

本主题中所做的工作是针对 OutputWnd 窗格,它运行得非常出色!我的 MDI 打开一个文件并使用 CTabView 并使用更新 6 中显示的 link 允许我在启动时更改选项卡。现在我让 OutputWnd 使用 OnApply 执行此操作,我正尝试在加载文件时将其应用于文档视图。当我调用在 TrainView.cpp 中创建的新函数并从 UserSettingsTabs.cpp 调用它时,我 运行 变成了 access violation。我以为是 static_cast 操作,但即使我使用 GetControlTabs() 做一个简单的 bold 也会崩溃(在注释掉的代码中显示,正在启动,现在在 OnApply 来检验理论)。很明显,我需要捕获 MDI 文档,但不确定如何完成。我认为这会很简单:

GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));

但是当它因 Cx000000005 访问冲突而崩溃时,我知道我这边出了问题。我无法修改 CTabView 操作,所以我想看看我们是否可以修复我做错的事情,让 OnApply 更改选项卡样式而不像我们在 OutputWnd 中所做的那样重新启动刚刚修复。

就目前而言,OnApply 正在运行,现在已修改以尝试集成 CTabView 功能:

SettingsUserTabs.cpp:

BOOL CSettingsUserTabs::OnApply()
{
    BOOL bResult = CMFCPropertyPage::OnApply();
    if (bResult)
    {
        AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
        ((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);     
        ((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();

        CTrainView* TrainTabs; // User Call from anywhere method
        TrainTabs->TrainDocUpdateTabsControl();
    }
    return bResult;
}

我添加了函数 CTrainView::TrainDocUpdateTabsControl() 来更新选项卡...其余代码已完全运行,即 void CTrainView::OnInitialUpdate()

TrainView.cpp:

IMPLEMENT_DYNCREATE(CTrainView, CTabView)

void CTrainView::OnInitialUpdate()
{
    CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
    pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: TrainView--CTrainView::OnInitialUpdate()."));

    // add views // cmb         
    AddView(RUNTIME_CLASS(CInformationView), AfxStringID(IDS_INFORMATION));
    AddView(RUNTIME_CLASS(CChaptersView), AfxStringID(IDS_CHAPTERS));

    // Nicely hack to access protected member
    class CMFCTabCtrlEx : public CMFCTabCtrl
    {
    public:
        void SetDisableScroll() { m_bScroll = FALSE; }
    };

    // One-Liner to Disable navigation control
    ((CMFCTabCtrlEx*)&GetTabControl())->SetDisableScroll();

    GetTabControl().EnableTabSwap(TRUE);
    GetTabControl().SetLocation(CMFCBaseTabCtrl::Location::LOCATION_BOTTOM);    
    //GetTabControl().SetActiveTabBoldFont(TRUE);
    GetTabControl().EnableAutoColor(TRUE);

    // Modify User Define tab style:
    int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
    
    // If the key doesn't exist, UserTableStyle will be 0 or FALSE;
    if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type

        int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
        GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));

    }
    else { // Default tabs style if Reg key does not exist i.e. new install/program reset

        GetTabControl().ModifyTabStyle(CMFCTabCtrl::STYLE_FLAT);
    }

    CTabView::OnInitialUpdate();    
}

void CTrainView::TrainDocUpdateTabsControl()
{   
    CTabView::AssertValid();
    GetTabControl().SetActiveTabBoldFont(TRUE); << CAUSES Cx000000005 ACCESS ERROR CRASH WHEN CALLED.

    //int EnumUserTabStyle;
    //int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
    //((CMainFrame*)AfxGetMainWnd())->GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle)); 
}

更新 6 编辑:

tabview 是从 Application.cpp 创建的,如下所示:

//Load Train Template
m_pkDocTrainTemplate = new CMultiDocTemplate(
    IDR_TRAIN, // Loads TRAIN operation
    RUNTIME_CLASS(CTrainDoc),
    RUNTIME_CLASS(CChildFrame),
    RUNTIME_CLASS(CTrainView));
AddDocTemplate(m_pkDocTrainTemplate);

我试图转换 m_pkDocTrainTemplate 因为我认为那是指针?因为是MDI,不知道是不是索引的问题,因为可以同时打开多个文档?

下面是我前段时间写的一个应用程序的例子。这是一个简单的“设置”对话框。与你的不同,这个来自 CDialogEx。但由于 CPropertyDialog 派生自 CDialog,这些也适用于您的情况。

使用向导,我将成员变量添加到对话框 class,绑定到对话框控件。在向导的“类别”组合中选择“值”而不是“控件”。这些在 class 定义中声明。为了简单起见,我只展示了三个。有一个 CString、一个 int 和一个 BOOL 变量,分别绑定到一个编辑、一个组合框(下拉列表)和一个复选框控件。

class CSettingsDlg : public CDialogEx
{
    .
    .
public:
    CString m_DBConn;
    int m_DumpSQL;
    BOOL m_bLineNums;
}

在实现中,向导修改了构造函数和DoDataExchange()个成员函数:

CSettingsDlg::CSettingsDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CSettingsDlg::IDD, pParent)
    , m_DBConn(_T(""))
    , m_DumpSQL(0)
    , m_bLineNums(FALSE)
{
}

void CSettingsDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_DBCONN, m_DBConn);
    DDV_MaxChars(pDX, m_DBConn, 255);
    DDX_CBIndex(pDX, IDC_COMBO_DUMPSQL, m_DumpSQL);
    DDV_MinMaxInt(pDX, m_DumpSQL, 0, 2);
    DDX_Check(pDX, IDC_CHECK_LINENUMS, m_bLineNums);
}

构造函数中的值是初始(默认)值。 DoDataExchange() 函数调用 DDX/DDV 例程。 DDX 例程执行数据传输(控制<->变量),而 DDV 例程执行验证 - 它们是可选的。 DoDataExchange() 函数被 UpdateData() 调用。此外,OnOK() 的默认实现调用 UpdateData(TRUE),如果成功则关闭对话框。

如果修改了某些内容,您将需要启用“应用”按钮。您可以捕获 EN_CHANGEEN_UPDATEBN_CLICKED 等通知消息(在 属性 编辑器中添加事件)并调用 SetModified() 函数 - 这可以证明很乏味,但我看不到任何其他方法。

这样的对话框class可以在应用中使用,如下图:

void CChildView::OnSetoptions()
{
    // Create a Settings-dialog class instance
    CSettingsDlg sd;  // Main application window as parent - will block every UI item in the application

    // Set initial values for the member variables
    sd.m_DBConn = szDBconn;
    sd.m_DumpSQL = nDumpSQL;
    sd.m_bLineNums = bDumpLineNums;

    if (sd.DoModal() == IDOK)
    {
        // Store the values entered by the user
        lstrcpyn(szDBconn, sd.m_DBConn, MAX_PATH);
        nDumpSQL = sd.m_DumpSQL;
        bDumpLineNums = sd.m_bLineNums;
    }
}


编辑:

我在上面的代码部分有一个用法示例。该过程是创建对话框的实例 class,设置成员变量的值(初始值,例如从注册表中读取),调用 DoModal(),如果成功将变量存储(复制)到其他地方(如果不丢弃它们)。这应该在某些事件处理程序中完成,例如 Update2 中的 CMainFrame::OnSettingsTools()CMainFrame::DoDataExchange() 覆盖没有意义。

您无法访问 m_style_tabs 变量,这很奇怪。它不是 CSettingsUserTabs class 的非静态 public 成员吗?它应该在 SettingsUserTabs.h 中声明。如果您以 pgeUserTabs.m_style_tabs 访问它,它不会工作吗?在 CSettingsUserTabs class 函数中,它可以简单地作为 m_style_tabs 访问。我还看到它在构造函数中被初始化为 FALSE 。它是 BOOL 而不是 int 吗? (顺便说一句,BOOL 在 Win32 中定义为 int,因此编译器不会抱怨)但是向导会为单选按钮生成一个 int 变量,还可以选择进行范围验证。

还有一点,你通常不需要在CSettingsUserTabs::OnTabRadioClicked()中调用UpdateData(TRUE)。请只留下 SetModified() 电话。 UpdateData(TRUE) 通常在 OnOK() 函数中调用。通常你不需要覆盖这些,因为默认实现就足够了。 CPropertyPage documentation btw 提到 OnApply 的默认实现调用 OnOK.



编辑 2:

OnApply()中您应该首先检查验证是否成功。此外, OnCreate() 不是可以直接调用的“方法”。它应该被认为是一个“事件”。创建 window 时,框架会调用它。您应该改为调用 Create()。在您的情况下,您可以销毁输出 Window 并重新创建它(使用新样式)。但是,我看到 CMFCTabCtrl class 有一个 ModifyTabStyle() 函数,您可以尝试调用它(无需破坏 windows 并重新创建它)。因此,您的代码将变为:

BOOL CSettingsUserTabs::OnApply()
{
    BOOL bResult = CMFCPropertyPage::OnApply();
    if (bResult)
    {
        AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
        ((CMainFraime*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
    }
    return bResult;
}

以上代码无法编译,因为 m_wndOutputm_wndTabs 成员受到保护。你必须让他们 public.