创建用于在 MFC 控件上使用 printf 样式字符串格式显示状态更新的函数

Posted

技术标签:

【中文标题】创建用于在 MFC 控件上使用 printf 样式字符串格式显示状态更新的函数【英文标题】:Creating a function for displaying status updates with printf style string formatting on an MFC control 【发布时间】:2021-08-13 04:28:19 【问题描述】:

我需要在对话框中显示状态更新,并希望能够向其发送printf 样式格式的字符串。此外,我希望该函数调用一个类似的函数,它将格式化数据添加到日志文件中。

假设我的静态控件名为IDC_MYSTATUSBAR,函数如下所示:

void MyDialog::ShowStatus(LPCWSTR lpText, ...)

    CString sMsg;
    va_list ptr;
    va_start(ptr, lpText);
    if (*ptr == 0)
       sMsg = lpText;
    else
       sMsg.FormatV(lpText, ptr);
    va_end(ptr);
    CWinApp *myApp = AfxGetApp();
    if (myApp)
    
        CWnd *pWnd = myApp->m_pMainWnd;
        if (pWnd)
        
            ::SetDlgItemText(pWnd->GetSafeHwnd(), IDC_MYSTATUSBAR, sMsg.GetString());
        
    
    myLogFunction(L"%s", sMsg.GetString());

函数调用如下:

ShowStatus(L"The results are %d.", 100);

ShowStatus(L"Server returned the following result: %s", L"result");

但是,其他模块和库通过一个名为 logfunc 的成员变量来调用我的函数,该成员变量定义为:

typedef void(*logFunc)(LPCWSTR lpText, ...);

在这种情况下,来自另一个模块的代码将如下所示:

logFunc m_logfunc;      
if (m_logfunc) m_logfunc(L"Internet Time: (%d) %s Local Time = %s", result, TimeResult.FormatGmt(L"%d.%m.%Y %H:%M"), CurrentTime.FormatGmt(L"%d.%m.%Y %H:%M"));
        

在大多数情况下,它可以完美运行,输出如下所示:

但是,有时会显示 % 开关而不是数据,然后它看起来像:

我试图进一步了解它失败的原因,所以失败的函数调用是:

int ct = GetCurrentTransactionNo();
if (m_logfunc)
    m_logfunc(L"Checking transaction no. %d.", ct);

但是如果我把它改成:

int ct = 15;
if (m_logfunc)
    m_logfunc(L"Checking transaction no. %d.", ct);

我得到了一个正确的格式化字符串。

【问题讨论】:

有了这个sn-p,我测试了,不能重现你的问题。您能否在没有私人信息的情况下显示a minimal, reproducible sample?并尝试捕捉任何可能的异常。 我会尝试两件事:将ct 设为常量,然后将%d 替换为%i。看看接下来会发生什么。 @flaviu2 - 这并没有改变任何东西。现在它显示“检查事务号。%i” 未定义的行为几乎是唯一的原因。如果您需要可靠的答案,请提供minimal reproducible example。 @IInspectable - 我用一个最小的可重现示例更新了这个问题。 【参考方案1】:

我认为正如其他人暗示的那样,问题在于未定义的行为。

您已经确定与 0 的值有关的问题会导致问题。嗯,0 也可以认为是nullptr(或空指针)。

我无法验证我的解释,但认为您的 0 参数列表可能被解释为空指针(无参数),因此返回值是未更改的格式字符串。

也许您应该考虑为您传递的值为0 时制定一个定制的解决方案,并以不同的方式处理它。例如(我不确定你是否可以使用同名的重载函数):

void MyDialog::ShowStatus2(LPCWSTR lpText, int iValue)

    CString sMsg;
    sMsg.Format(lpText, iValue);
    CWinApp *myApp = AfxGetApp();
    if (myApp)
    
        CWnd *pWnd = myApp->m_pMainWnd;
        if (pWnd)
        
            ::SetDlgItemText(pWnd->GetSafeHwnd(), IDC_MYSTATUSBAR, sMsg.GetString());
        
    
    myLogFunction(L"%s", sMsg.GetString());


我已经这样做了,在有问题的情况下,我可以看到在调用FormatV sMsg 后包含一些“%”字符。换句话说,FormatV 失败了,但我不知道为什么。

当您处于调试模式时,您可能会发现查看 ptr 的值是有用的,因为它包含已解析的参数。当此参数传入的值为0 时,需要查看ptr 是否为空。

【讨论】:

问题是我不仅传递整数或一个整数,而且传递任何类型的各种组合,所以我看不到创建一个单独的 ShowStatus2() 只是为了处理一个 int 参数.整个想法是支持任意类型和任意数量的参数。 @MichaelHaephrati 我的朋友的观点是,可以将具有值0 的单个int 视为null。所以这是一个你必须考虑的特殊情况。 知道了,但是有几个参数第一个是 0 呢?在某些情况下,%s 也没有得到正确处理。 @MichaelHaephrati 我对可变参数机制的逻辑了解得不够多,无法评论这方面。 va_list 不是这样工作的。在内部,它只是一个指针。没有关于可用参数数量的信息,也没有任何特殊的逻辑试图确定列表的结尾。驱动程序是格式字符串,每当遇到占位符时,它都会使指针前进。它不关心新指针是否有效。它只是使用它,愉快地支持您探索未定义行为的广阔空间。 C++ 用十年前在 C++11 中引入的参数包解决了这个问题。【参考方案2】:

当传递给函数的第一个参数为0或“”时,*ptr(实际上是*ptr[0]*(ptr + 0),因此是内存中的第一个块)将保持null,因此函数打印字符串从字面上看,没有使用FormatV() 对其进行格式化。

你需要替换下面的块

if (*ptr == 0)
    sMsg = lpText;
else
    sMsg.FormatV(lpText, ptr);

只有

sMsg.FormatV(lpText, ptr);

【讨论】:

您试图修复不需要修复的代码。这样做会使事情变得更糟,因为您选择了 ANSI 编码并在格式化字符串上设置了 99 个代码单元的任意限制。这没用。 另外,是否值得解释比较 *ptr 和 ptr 再次为 null 之间的区别?让他明白为什么错了?但修复它做得很好。 ? @AndrewTruckle,当传递给函数的第一个参数为0或“”时,*ptr等于0,因为内存中的第一个块将保持null。【参考方案3】:

您可以使用 C++11 中引入的参数包解决此问题

class Temp


public:
  template< typename ... Type >
  static void ShowStatus( LPCWSTR lpText, Type ... args )
  
    CString sMsg;
    sMsg.Format( lpText, args... );
    MessageBox( NULL, sMsg.GetString(), L"", MB_OK );
  
;


int main()

  int ct = 0;
  wchar_t test[] = L"this is a test";
  Temp::ShowStatus( L"%d", ct );
  Temp::ShowStatus( L"%s", test );
  Temp::ShowStatus( L"%d %s", ct, test );

【讨论】:

以上是关于创建用于在 MFC 控件上使用 printf 样式字符串格式显示状态更新的函数的主要内容,如果未能解决你的问题,请参考以下文章

MFC 中关于Edit控件问题

Windows API编程(不是MFC)用CreateWindowEx创建的按钮等子窗口控件是默认的Windows经典样式,如何改变?

使用 MFC,如何为带有圆角和阴影的 CEdit 控件设置样式?

MFC 中的 OwnerDrawn 控件

[ MFC ] 对话框动态控件的创建 在Picture Control控件上显示图片 [大三TJB_708]

MFC(我用的VS2008)中怎样改变picture控件(picture control)的边框样式???我要通过代码设置的