Unlike the WriteProfileString method, the GetProfileString method does not offer a way to check if a section or an option even exists in the profile. All I can get is a default string. I am trying to implement a class representing my profile and I would like to have a
BOOL HasSection(CString sSection)
and a
BOOL HasOption(CString sSection, CString sOption)
method in it.
It is very easy to do. Please read the MSDN Article carefully. Notice that when you pass NULL to first or a second parameter, the function returns a list of all section names (in the former case) or keys (in the latter case).
So for you to implement HaseOption() method you would have a code similar to this one:
BOOL HasSection(CString sSection)
{
DWORD dwSize = 100, dwRequired;
LPTSTR lpBuffer = new TCHAR[dwSize];
BOOL bExists = FALSE;
dwRequired = ::GetProfileString(NULL, NULL, _T(""), lpBuffer, dwSize);
while(dwRequired == dwSize - 2)
{
// buffer is too small
delete [] lpBuffer;
dwSize = dwRequired + 100;
lpBuffer = new TCHAR[dwSize];
dwRequired = ::GetProfileString(NULL, NULL, _T(""), lpBuffer, dwSize);
}
if(dwRequired)
{
LPTSTR lpszFound = lpBuffer;
do
{
if(sSection.CompareNoCase(lpszFound) == 0)
{
bExists = TRUE; break;
}
} while(*(lpszFound = _tcsninc(lpszFound, _tcsnbcnt(lpszFound, dwRequired)+1)));
}
delete [] lpBuffer;
return bExists;
}
I have not tested the code, just cut-n-pasted from my program and modified a bit. Please test the code yourself
Related
Code:
void CChristianLifeMinistryEditorDlg::OnSize(UINT nType, int cx, int cy)
{
CResizingDialog::OnSize(nType, cx, cy);
const CWnd* pFocus = GetFocus();
CComboBox* pFocusCombo = nullptr;
if (pFocus != nullptr)
{
if (pFocus->GetParent()->IsKindOf(RUNTIME_CLASS(CComboBox)))
{
pFocusCombo = dynamic_cast<CComboBox*>(GetFocus()->GetParent());
}
}
for (CWnd* pWnd = GetWindow(GW_CHILD); pWnd != nullptr; pWnd = pWnd->GetNextWindow(GW_HWNDNEXT))
{
if (pWnd == pFocusCombo)
{
// TODO: Sadly, by now, the control has already got all the text selected.
//pFocusCombo->SetEditSel(LOWORD(dwEditSel), HIWORD(dwEditSel));
}
else if (pWnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
{
// This only works for combo boxes that are bound to controls
auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
else
{
CString strClassName;
if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
{
if (strClassName == _T("ComboBox"))
{
auto* pCombo = (CComboBox*)pWnd;
//auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
}
strClassName.ReleaseBuffer();
}
}
if (m_pHtmlPreview != nullptr)
{
m_lblHtmlPreview.GetWindowRect(m_rctHtmlPreview);
ScreenToClient(m_rctHtmlPreview);
m_pHtmlPreview->MoveWindow(m_rctHtmlPreview);
}
}
I display the whole function for context. But I am specifically interested in this bit:
CString strClassName;
if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
{
if (strClassName == _T("ComboBox"))
{
auto* pCombo = (CComboBox*)pWnd;
//auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
}
strClassName.ReleaseBuffer();
During code analysis updates I had many situations where I had to update C-Style casts. A lot of the time I was able to use static_cast, but, in some instances the compiler would then tell me I should use dynamic_cast.
I then found that my application was not working correctly and in debug mode isolated it to this bit:
//auto* pCombo = (CComboBox*)pWnd;
auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
It turned out that the cast pointer pCombo was null. Yet, this never happens when I use the C-Style cast. As a result I have reverted to the C-Style cast. I saw this discussion (MFC Classes and C++ style casts) but I can't see that this is the reason (temporary pointers).
What cast should I be using that I can rely on in this situation?
C-style casting will try different c++ casting, it may choose reinterpret_cast static_cast. This is converting CWnd* to CComboBox*, for example:
CWnd* wnd = GetDlgItem(IDC_COMBO1);
CComboBox* combo = (CComboBox*)wnd;
In general, parent class can't "always" be converted to child. It depends if CComboBox m_combobox; for that ID was created.
A) m_combobox does exist:
In this case our wnd can be a reference to m_combobox.
CWnd::GetDlgItem etc. cast &m_combobox to CWnd*, they pass it around.
dynamic_cast checks it and converts back to CComboBox*.
MFC's IsKindOf will confirm if m_combobox was created.
B) m_combobox doesn't exist:
In this case our wnd is CWnd* object. MFC never created CComboBox for that control.
dynamic_cast tests it, can't convert to CComboBox*
static_cast works if wnd's classname is "ComboBox"
The code below should be okay. In this case you can skip dynamic_cast if you want, rely on static_cast. But it's better to use dynamic_cast if possible.
CComboBox* ptr = nullptr;
if (wnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
ptr = dynamic_cast<CComboBox*>(wnd);
if(!ptr)
{
CString classname;
::GetClassName(wnd->m_hWnd, classname.GetBuffer(_MAX_PATH), _MAX_PATH);
classname.ReleaseBuffer();
if(classname == L"ComboBox")
ptr = static_cast<CComboBox*>(wnd);
}
My code:
void CAssignSelectedColumnDlg::AddColumnData(CString strHeading, CStringArray* pAryStrNames, int iColumnIndex, int iCustomIndex /*-1*/, BOOL bFixedCustomType /*FALSE*/)
{
//COLUMN_DATA_S *psData = nullptr;
//psData = new COLUMN_DATA_S;
auto psData = std::make_unique<COLUMN_DATA_S>();
if (psData != nullptr)
{
// Note: we don't "own" pAryStrNames
psData->strHeading = strHeading;
psData->pAryStrNames = pAryStrNames;
psData->sActionListInfo.byColumnIndex = iColumnIndex;
psData->sActionListInfo.byCustomIndex = iCustomIndex;
psData->sActionListInfo.bFixedCustomType = bFixedCustomType ? true : false;
m_aryPtrColumnData.Add(psData);
}
}
Code analysis was suggesting I use std::make_unique instead of new. So I adjusted teh code but now get a compile error. I tried to find a suitable approach:
No suitable conversion function from std::unique_ptr<COLUMN_DATA_S>, std::default_delete<COLUMN_DATA_S>> to void * exists.
The function in question is CPtrArray::Add. I am still trying to gras the smart pointers and their usage in a context like this.
Note that we should not be deleting the pointer when the function ends. The deletion of the collection is done in a dialog event handler.
I have come up with the following function which works as it should:
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode)
{
bool bHandled = false;
map<HWND, CComboBox*> mapControls;
map<HWND, CString*> mapControlsText;
mapControls.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_cbMaterialAssignment1);
mapControls.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_cbMaterialAssignment2);
mapControls.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_cbMaterialAssignment3);
mapControls.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_cbMaterialAssignment4);
mapControlsText.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_strMaterialAssignment1);
mapControlsText.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_strMaterialAssignment2);
mapControlsText.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_strMaterialAssignment3);
mapControlsText.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_strMaterialAssignment4);
if (mapControls.find(::GetParent(hWnd)) != mapControls.end())
{
UpdateData(TRUE);
DWORD dwSel = mapControls[::GetParent(hWnd)]->GetEditSel();
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[::GetParent(hWnd)],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
bHandled = true;
}
else
{
map<HWND, CEdit*> mapControls;
map<HWND, CString*> mapControlsText;
mapControls.emplace(m_editBibleReading.GetSafeHwnd(), &m_editBibleReading);
mapControls.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_editDiscussionVideoTheme);
mapControls.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_editDiscussionVideoMaterial);
mapControlsText.emplace(m_editBibleReading.GetSafeHwnd(), &m_strBibleReading);
mapControlsText.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_strDiscussionVideoTheme);
mapControlsText.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_strDiscussionVideoMaterial);
if (mapControls.find(hWnd) != mapControls.end())
{
UpdateData(TRUE);
DWORD dwSel = mapControls[hWnd]->GetSel();
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[hWnd],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
bHandled = true;
}
}
return bHandled;
}
The code is straight forward to follow. But as you can see I have to potentially deal with either a comb box edit control or regular edit control. As a result, I have two sets of similar code.
Is it possible to consolidate some of this code without overcomplicating it too much? My project is set to the ISO C++ 17 Standard if that helps.
Update
Initially I thought I would try a single map of CWnd* pointers. But then I had the two problems of CComboBox verses CEdit.
CComboBox uses:
::GetParent(hWnd)
GetEditSel()
CEdit uses:
hWnd
GetSel()
By using a single list of CWnd* I no longer know which is a combo or a edit control.
—-
Update
The core problem I would like to solve is to have a single loop rather than the two.
One possibility would be to define an interface to the functionality you need, then a couple of implementations of that functionality. Add in a map to get from an HWND to the object you need, and you're off to the races:
class Writer {
virtual DWORD getSel() = 0;
CString* data;
public:
Writer(CString *data) : data(data) {}
void write(CString text) {
UpdateData(true);
DWORD selection = getSel();
CMeetingScheduleAssistantApp::EncodeText(*data, text, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(false);
}
virtual ~Writer() = default;
};
class ComboBoxWriter : public writer {
CWnd *parent;
DWORD getSel() override { return parent->GetEditSel(); }
public:
ComboBoxWriter(CComboBox &dest, CString &data) : Writer(&data), parent(dest.GetParent()) {}
};
class EditCtrlWriter : public Writer {
CEdit *ctrl;
DWORD getSel() override { return ctrl->GetSel(); }
public:
EditCtrlWriter(CEdit &ctrl, CString &data) : Writer(&data), ctrl(&ctrl) {}
};
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode) {
static std::map<HWND, Writer*> controls {
{ m_cbMaterialAssignment1.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment1, &m_strMaterialAssignment1) },
{ m_cbMaterialAssignment2.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment2, &m_strMaterialAssignment2) },
{ m_cbMaterialAssignment3.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment3, &m_strMaterialAssignment3) },
{ m_cbMaterialAssignment4.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment4, &m_strMaterialAssignment4) },
{ m_editBibleReading.GetSafeHwnd(), new EditCtrlWriter(&m_editBibleReading, &m_strBibleReading) },
{ m_editDiscussionVideoTheme.GetSafeHwnd(), new EditCtrlWriter(&m_editDiscussionVideoTheme, &m_strDiscussionVideoTheme) },
{ m_editDiscussionVideoMaterial.GetSafeHwnd(), new EditCtrlWriter(&m_editDiscussionVideoMaterial, &m_strDiscussionVideoMaterial) }
};
auto ctrl = controls.find(hwnd);
if (ctrl == controls.end())
return false;
ctrl->second->write(strCode);
return true;
}
This isn't a lot shorter overall (in fact, it's almost the same length), but quite a bit more of that length is boilerplate that's pretty easy to ignore.
Your building maps of HWND to MFC controls looks very strange.
Surely MFC already has that map. See if this helps: https://learn.microsoft.com/en-us/cpp/mfc/accessing-run-time-class-information?view=msvc-160
Based on the comments to one of the answers (#vlad-feinstein):
all HWND handles are unique system-wide
I decided that I could take a much simpler approach to simplifying my code:
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode)
{
map<HWND, CWnd*> mapControls; // Use generic CWnd pointers
map<HWND, CString*> mapControlsText;
// Lookup map of controls
mapControls.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_cbMaterialAssignment1);
mapControls.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_cbMaterialAssignment2);
mapControls.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_cbMaterialAssignment3);
mapControls.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_cbMaterialAssignment4);
mapControls.emplace(m_editBibleReading.GetSafeHwnd(), &m_editBibleReading);
mapControls.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_editDiscussionVideoTheme);
mapControls.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_editDiscussionVideoMaterial);
// Lookup map of text values
mapControlsText.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_strMaterialAssignment1);
mapControlsText.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_strMaterialAssignment2);
mapControlsText.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_strMaterialAssignment3);
mapControlsText.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_strMaterialAssignment4);
mapControlsText.emplace(m_editBibleReading.GetSafeHwnd(), &m_strBibleReading);
mapControlsText.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_strDiscussionVideoTheme);
mapControlsText.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_strDiscussionVideoMaterial);
// Determine if the use clicked on a combo or edit control
bool bIsComboControl = false;
HWND hWndToUse = nullptr;
if (mapControls.find(::GetParent(hWnd)) != mapControls.end())
{
bIsComboControl = true;
hWndToUse = ::GetParent(hWnd);
}
else if (mapControls.find(hWnd) != mapControls.end())
hWndToUse = hWnd;
if (hWndToUse == nullptr)
return false;
// Process
UpdateData(TRUE);
// Get the correct selection from the control
DWORD dwSel = (bIsComboControl) ?
((CComboBox*)mapControls[hWndToUse])->GetEditSel() :
((CEdit*)mapControls[hWndToUse])->GetSel();
// Encode the text
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[hWndToUse],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
return true;
}
The code is below and it is part of a thread. pFileChange->m_hDirectory is of type HANDLE and pFileChange->m_eventFileChange if of type CEvent. CreateFile and ReadDirectoryChangesW return with a success. I am not able to figure out why i am getting an invalid handle status, please help!
UINT CFileChange::FileMontiorThread(LPVOID pArgs)
{
CFileChange* pFileChange = NULL;
pFileChange = (CFileChange*)pArgs;
pFileChange = (CFileChange*)pArgs;
CString str = pFileChange->m_strDirectory;
LPSTR strDirectory;
strDirectory = str.GetBuffer(str.GetLength());
PathRemoveFileSpec(strDirectory);
DWORD dwBytes = 0;
vector<BYTE> m_Buffer;
BOOL m_bChildren;
OVERLAPPED m_Overlapped;
HANDLE arrHandles[2] = { pFileChange->m_hDirectory, pFileChange->m_eventFileChange };
::ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
DWORD dwBufferSize = 16384;
m_Buffer.resize(dwBufferSize);
m_bChildren = false;
pFileChange->m_hDirectory = ::CreateFile(
(LPCSTR)(LPCTSTR)strDirectory,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED,
NULL);
if (pFileChange->m_hDirectory == INVALID_HANDLE_VALUE)
{
return false;
}
BOOL success = ::ReadDirectoryChangesW(
pFileChange->m_hDirectory, // handle to directory
&m_Buffer[0], // read results buffer
m_Buffer.size(), // length of buffer
m_bChildren, // monitoring option
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, // filter conditions
&dwBytes, // bytes returned
&m_Overlapped, // overlapped buffer
NULL); // no completion routine
DWORD dwWaitStatus;
while (!pFileChange->m_bKillThread)
{
dwWaitStatus = WaitForMultipleObjects(2, arrHandles, FALSE, INFINITE);
Switch(dwWaitStatus)
{
case WAIT_FAILED:
{
ULONG rc = 0;
rc = ::GetLastError();
LPVOID lpMsgBuf = NULL;
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
rc,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
CString strErrMsg;
strErrMsg.Format(_T("%s, %s, Reason:%s"), "", "", (LPTSTR)lpMsgBuf);
break;
}
}
}
return 0;
}
Note that, as it specifies in the documentation, you can't wait on any type of handle.
Waiting on a directory handle isn't going to do what you think it should. Read this related question and its answers for more information and background reading.
As it seems you're trying to create a folder monitor, perhaps read this blog post for the correct way to use ReadDirectoryChangesW.
The answer was provided by Hans Passant in a comment:
You copy the handles into arrHandles too soon, before they are created. That cannot work of course.
Is it possible to compare two String.Index values in Swift? I'm trying to process a string character by character, and several times I need to check if I am at the end of the string. I've tried just doing
while (currentIndex < string.endIndex) {
//do things...
currentIndex = currentIndex.successor()
}
Which complained about type conversions. Then, I tried defining and overload for < as such:
#infix func <(lhs: String.Index, rhs: String.Index) -> Bool {
var ret = true //what goes here?
return ret
}
Which gets rid of compilation errors, but I have no clue what to do in order to compare lhs and rhs properly. Is this the way I should go about using String.Index, or is there a better way to compare them?
The simplest option is the distance() function:
var string = "Hello World"
var currentIndex = string.startIndex
while (distance(currentIndex, string.endIndex) >= 0) {
println("currentIndex: \(currentIndex)")
currentIndex = currentIndex.successor()
}
Beware distance() has O(N) performance, so avoid it for large strings. However, the entire String class doesn't currently handle large strings anyway — you should probably switch to CFString if performance is critical.
Using an operator overload is a bad idea, but just as a learning exercise this is how you'd do it:
var string = "Hello World"
var currentIndex = string.startIndex
#infix func <(lhs: String.Index, rhs: String.Index) -> Bool {
return distance(lhs, rhs) > 0
}
while (currentIndex < string.endIndex) {
currentIndex = currentIndex.successor()
}
String indexes support = and !=. String indexes are an opaque type, not integers and can not be compared like integers.
Use: if (currentIndex != string.endIndex)
var currentIndex = string.startIndex
while (currentIndex != string.endIndex) {
println("currentIndex: \(currentIndex)")
currentIndex = currentIndex.successor()
}
I believe this REPL/Playground example should illuminate what you (and others) need to know about working with the String.Index concept.
// This will be our working example
let exampleString = "this is a string"
// And here we'll call successor a few times to get an index partway through the example
var someIndexInTheMiddle = exampleString.startIndex
for _ in 1...5 {
someIndexInTheMiddle = someIndexInTheMiddle.successor()
}
// And here we will iterate that string and detect when our current index is relative in one of three different possible ways to the character selected previously
println("\n\nsomeIndexInTheMiddle = \(exampleString[someIndexInTheMiddle])")
for var index: String.Index = exampleString.startIndex; index != exampleString.endIndex; index = index.successor() {
println(" - \(exampleString[index])")
if index != exampleString.startIndex && index.predecessor() == someIndexInTheMiddle {
println("current character comes after someIndexInTheMiddle")
} else if index == someIndexInTheMiddle {
println("current character is the one indicated by someIndexInTheMiddle")
} else if index != exampleString.endIndex && index.successor() == someIndexInTheMiddle {
println("Current character comes before someIndexinTheMiddle")
}
}
Hopefully that provides the necessary information.
Whatever way you decide to iterator over a String, you will immediately want to capture the iteration in a function that can be repeatedly invoked while using a closure applied to each string character. As in:
extension String {
func each (f: (Character) -> Void) {
for var index = self.startIndex;
index < self.endIndex;
index = index.successor() {
f (string[index])
}
}
}
Apple already provides these for C-Strings and will for general strings as soon as they get character access solidified.