首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

MFC CStdioFile 讀取文本文件內容異常:OnEnChange 觸發遞歸調用導致讀取失敗

     編輯:程式設計疑難解答

問題現象

  • `file.Open()` 成功,`file.ReadString(str)` 執行
  • 調試顯示 `str` 顯示「字符串中的字符無效」
  • 文件路徑正確,文件內容正常

根本原因

在 `content += str;` 賦值時,觸發了編輯控件的 `EN_CHANGE` 通知消息,進而調用 `OnEnChange()` 處理函數,該函數內部調用了 `writein()` 寫入操作,**導致讀取過程中被寫入操作打斷/干擾**,造成數據異常。

代碼還原

void EditView::ReadTitle(CString strName) {
    CString content = "";
    CStdioFile file;
    CString fileName = strName;
    
    if (file.Open(fileName, CFile::modeRead | CFile::typeText)) {
        file.SeekToBegin();
        CString str;
        int len = 512 - 2;
        file.ReadString(str);  // 讀取一行
        
        if ("" != str) 
            content += str;  // 💥 此處觸發 OnEnChange -> writein()
        
        file.Close();
    } else {
        AfxMessageBox("讀取失敗!");
    }
    this->SetWindowText(content);  // 設置窗口文本
}

解決方案

方案一:臨時屏蔽 EN_CHANGE 通知(推薦)

void EditView::ReadTitle(CString strName) {
    CString content = "";
    CStdioFile file;
    
    if (file.Open(strName, CFile::modeRead | CFile::typeText)) {
        // 1. 斷開 EN_CHANGE 消息映射(或設置標志位)
        // 方法 A:修改消息映射表(需重新編譯)
        // 方法 B:使用標志位(運行時動態控制)
        m_bReadingFile = true;  // 類成員變量 BOOL m_bReadingFile = FALSE;
        
        file.SeekToBegin();
        CString str;
        while (file.ReadString(str)) {  // 循環讀取所有行
            if (!str.IsEmpty()) {
                content += str;
                content += _T("\r\n");  // 保留換行
            }
        }
        file.Close();
        
        m_bReadingFile = false;  // 恢復
        
        // 2. 統一設置文本(單次觸發 EN_CHANGE)
        SetWindowText(content);
    } else {
        AfxMessageBox(_T("讀取失敗!"));
    }
}

// OnEnChange 中判斷標志位
void EditView::OnEnChange() {
    if (m_bReadingFile) return;  // 讀取文件期間忽略變更通知
    
    // 正常業務邏輯
    writein();
}

方案二:分離讀取與設置

void EditView::ReadTitle(CString strName) {
    CString content;
    CStdioFile file;
    
    if (file.Open(strName, CFile::modeRead | CFile::typeText)) {
        CString str, line;
        while (file.ReadString(line)) {
            str += line;
            str += _T("\r\n");
        }
        file.Close();
        content = str;  // 先組裝完整內容
    }
    
    // 單次設置,避免多次觸發
    SetWindowText(content);
}

方案三:使用 CFile 直接讀取(繞過 CStdioFile 緩衝)

CFile file;
if (file.Open(strName, CFile::modeRead | CFile::typeText)) {
    ULONGLONG len = file.GetLength();
    CString content;
    content.GetBufferSetLength(len + 1);
    file.Read(content.GetBuffer(len), len);
    content.ReleaseBuffer();
    file.Close();
    SetWindowText(content);
}

最佳實踐總結

  1. 讀取文件時屏蔽 UI 更新通知:標志位法最靈活,無需修改消息映射
  2. 批量讀取後單次設置:減少 `SetWindowText` 調用次數,避免多次觸發 `EN_CHANGE`
  3. 區分「用戶輸入」與「程序設置」:`m_bProgrammaticChange` 標志位區分來源
  4. 大文件考慮異步讀取:避免阻塞 UI 線程,使用 Worker Thread + PostMessage

調試技巧

// 在 OnEnChange 開頭添加調試輸出
void EditView::OnEnChange() {
    TRACE(_T("OnEnChange called, m_bReadingFile=%d\n"), m_bReadingFile);
    if (m_bReadingFile) return;
    // ...
}

通過 Output 窗口觀察調用堆棧,快速定位遞歸調用源頭。

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