创建用于在 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 样式字符串格式显示状态更新的函数的主要内容,如果未能解决你的问题,请参考以下文章
Windows API编程(不是MFC)用CreateWindowEx创建的按钮等子窗口控件是默认的Windows经典样式,如何改变?
使用 MFC,如何为带有圆角和阴影的 CEdit 控件设置样式?
[ MFC ] 对话框动态控件的创建 在Picture Control控件上显示图片 [大三TJB_708]
MFC(我用的VS2008)中怎样改变picture控件(picture control)的边框样式???我要通过代码设置的