Multithreading and MFC - c++

I am writing a multithreaded app. On the main thread is the main window which is a modeless dialog box. When the user clicks on the start button, it will create a new thread that does some stuff for a long time. On the main thread it will create a new modeless dialog box to display the status of that new thread, 1 per thread. I created kind of a template dialog box using the resource editor and I set a static text for the status to have an id of IDC_STATIC_NUMCYCLE. I poll the status of the threads during the OnIdle function. The updating of the status works with 1 thread only, but if I spawn more the static text for all will not update until the very end or if it is the only thread left running.
Declaration:
map<CSimDlg *, CSimulator *> simulations;
My OnIdle function:
BOOL CFGSim1App::OnIdle(LONG lCount)
{
CWinApp::OnIdle(lCount);
DWORD exitCode;
CString numOfCycle;
for (map<CSimDlg *, CSimulator *>::iterator iter = simulations.begin(); iter != simulations.end();)
{
// skip already finished threads
if (iter->second == NULL)
{
iter++;
continue;
}
if (GetExitCodeThread(iter->second->m_hThread, &exitCode))
{
if (exitCode == 0)
{
delete iter->second;
iter->second = NULL;
if (IsWindow(iter->first->m_hWnd))
{
iter->first->SetDlgItemText(IDC_STATIC_SIMSTATUS, L"Simulation done");
}
else
{
iter = simulations.erase(iter);
}
}
else
{
ULONG64 temp = iter->second->m_ul64NumOfCycle;
if (temp % 10000 == 0)
{
numOfCycle.Format(_T("%d"), temp);
iter->first->SetDlgItemText(IDC_STATIC_NUMCYCLE, numOfCycle);
}
iter++;
}
}
else
{
iter++;
}
}
return TRUE;
}
I am guessing the problem is with the id of the static text. Is there a way to work around this? Or do I need to declare a different id for each dialog box? Or is the problem elsewhere?

The (temp % 10000 == 0) condition is suspicious to me. You are assuming that temp will increment slowly enough that 10000 marks will be detected. This may not be the case. If you want to reduce GUI operations then introduce a "last count" variable for each thread, and update GUI only if temp is sufficiently larger than this variable, that is then set to temp.
BTW you don't need std::map if all you do with the container is to traverse trough it, and don't use special features of the map. It might as well be a list of std::pair, or list of some new structure. This new structure could hold the mentioned last count variable.

Your logic seems fine to me.
Maybe MFC could require a repaint request after the text change:
iter->first->SetDlgItemText(IDC_STATIC_NUMCYCLE, numOfCycle);
iter->first->Invalidate();
I'm sorry but currently I don't have MFC handy to test...

Related

Array receives two of the same values

Sorry if the code is sloppy, I've only been working with QT for the past couple of weeks. I'm working on a small game while studying, and right now I'm working on when the player buys an item from the shop it will be placed it into the character bag.
The Problem is when I buy the item once, it works fine. But if I delete the item after purchase then repurchase the same item again it will put two of the same item into the array even though I purchased one.
I have 3 files used for this. Inventory.cpp, GameScreen.cpp, Shops.cpp.
GameScreen.cpp is the main file. Here I just initialize everything.
void GameScreen::initGame(QString &characterName, QString &characterProfession){
//PASS CHARACTERNAME AND CHARACTERPROFESSION INTO INVENTORY
inv.initCharacter(characterName, characterProfession);
//INIT BAG
inv.initBag();
//INIT MONEY
inv.initMoney();
//SHOPS INITS
mos.initShop();
mos.passMoneyToShop(inv.gold, inv.silver, inv.copper);
}
This will open the shop window. The Item connect is apart of my problem.
void GameScreen::on_mapOneShopB_clicked()
{
mos.setModal(true);
//SEND THE ITEM INTO BAG
connect(&mos, SIGNAL(getItemFromMapOneShop(const QString&)), &inv, SLOT(bagAddElement(const QString&)));
mos.show();
mos.exec();
}
Moving into Shops.cpp. I use polymorphism in this file and that's the reason why you see MapOneShop:: instead of Shops::. Sorry about the confusion. But moving on, I hit the button and it subtracts the item price from my amount of gold.
void MapOneShop::on_buyB_clicked()
{
//ONLY WORKS IF I HIT THE ITEM NAME COLUMN THEN HIT BUY
gold -= itemPrice[ui->treeWidget->currentColumn()];
//UPDATE INFORMATION
updateInformationVAndMoneyAfterBuy();
}
It then moves into updateInformationVAndMoneyAfterBuy(); which checks that I spent money, and If I did it will emit the item I need. I just emit back to GameScreen.cpp inside of void GameScreen::on_mapOneShopB_clicked() and pass them into Inventory.cpp.
void MapOneShop::updateInformationVAndMoneyAfterBuy()
{
//UPDATE INFORMATIONV FIRST TO CHECK MONEY CHANGES
if(goldCheck != gold)
{
emit getUpdatedMoneyFromShop(gold, silver, copper);
//ITEM NAME IS WHERE I STORE THE NAMES OF THE ITEMS
emit getItemFromMapOneShop(this->itemName[ui->treeWidget->currentColumn()]);
}
}
Now moving into Inventory.cpp. When the item gets passed into bagAddElement after deleting then repurchasing, I get two of the same items even though only 1 should be passed in. I am using a dynamic array for this. Below I will also show the void Inventory::on_deleteB_clicked, void Inventory::bagDeleteAt and also void Inventory::bagLWPrint functions. The items value is my array holding the shopItem strings.
void Inventory::bagAddElement(const QString& shopItem)
{
//I USE THIS TO CHECK THE VALUE shopItem.
qDebug()<<"bagAddElement: " << shopItem;
//IF THE CURRENT POSITION IN THE BAG
//IS BIGGER THAN THE CURRENT SIZE
//IT WILL INCREASE THE BAG FOR US
if(nrOfEl >= bagSize)
{
bagExpand();
}
//CHECK FOR VALUES INSIDE ARRAY AND CHECK IF THEY ARE NULL
//IF SO, IT WILL ADD THE ITEM INTO THE NULL POSITION
for(int i = 0; i < bagSize; i++)
{
if(items[i] == nullptr)
{
nrOfEl = i;
items[nrOfEl++] = shopItem;
break;
}
}
//UPDATE WIDGET LIST
bagLWPrint();
}
Here I pass the currently selected row value inside bagLW and pass it into void Inventory::bagDeleteAt.
void Inventory::on_deleteB_clicked()
{
//BagLW IS A LIST WIDGET
bagDeleteAt(bagLW->currentRow());
}
Now we look for the what's inside items[row] and set it to nullptr. Then we go into void Inventory::bagLWPrint.
void Inventory::bagDeleteAt(int row)
{
if(items[row] == nullptr)
{
//IF THE ITEM IS ALREADY NULL WILL PRINT A MESSAGE
QMessageBox::information(this,"Bag","No item in that slot");
}
else
{
//SET CURRENTLY SELECTED ITEM TO NULL
items[row] = nullptr;
bagLWPrint();
}
}
I clear bagLW and refill it with the updated array.
void Inventory::bagLWPrint()
{
bagLW->clear();
for(int i = 0; i < bagSize; i++)
{
if(items[i] != nullptr)
{
bagLW->addItem(items[i]);
}
if(items[i] == nullptr)
{
bagLW->addItem(items[i]);
}
}
}
Other notes.
-The item only gets doubled when inserted into void Inventory::bagAddElement.
-I tried adding another item inside of void GameScreen::initGame and it did not double after deleting and re-entering.
Sorry for such a long question. And any help would be much, much appreciated.
void Inventory::bagExpand()
{
//1: INCREASE BAGSPACE
bagSize *= 2;
//2: CREATE TEMP ARRAY
QString *tempItems = new QString[bagSize];
//3: COPY OVER VALID VALUES FROM OLD ARRAY
for(int i = 0; i < nrOfEl; i++)
{
tempItems[i] = items[i];
}
//4: DELETE OLD ARRAY MEMORY
delete[] items;
//5: POINT OLD ARRAY POINTER TO NEW ARRAY LOCATION
items = tempItems;
//PRINT BAGLW - (UDPATE)
bagLWPrint();
qDebug()<<"Bag has increased";
}
The problem is that you connect and re-connect the getItemFromMapOneShop signal on every button click. That means that on first click, you add it once (signal triggered, 1 slot connected). On the second click, you add it twice (signal triggered, 2 slots connected). And so on.
QObjects manage a list of all connected slots per signal, and call each of them. Connecting a slot multiple times will call it that many times
You should connect the singal only once, e.g. in the constructor; or disconnect the signal when no longer used
As #Andéon Evain pointed out, you could also use Qt::UniqueConnection. This will not add a duplicate connection if it already exists (considering sender, signal, receiver, slot). That might be useful for cases where it's unknown if already connected; not in your simple case

Blocking signal one time in Qt doesn't work correctly

Hi i have following code:
void MainWindow::on_listWidgetNotes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)//Test!
{
if(current != NULL)
{
ui->plainTextEditContent->setEnabled(true);
change = false;
if(isModified)
{
auto reply = QMessageBox::question(this, "Test", "Do you want save changes?", QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
if (reply == QMessageBox::Yes) on_pushButtonSave_clicked();
else if(reply == QMessageBox::No) notes.closeFile();
else
{
//ui->listWidgetNotes->blockSignals(true);
ui->listWidgetNotes->setCurrentItem(previous);
//ui->listWidgetNotes->blockSignals(false);
return;
}
}
isModified = false;
this->setWindowTitle(current->text()+" - VfNotes 1.0");
ui->plainTextEditContent->setPlainText(notes.openFile(current->text()));
}
}
In specified case code have to show message box and set focus on previous item, after select cancel button.
But setCurrentItem calls on_listWidgetNotes_currentItemChanged again with this message box. After use blockSignals focus doesn't come back on previous element. What to do to set focus on previous item after click cancel, that on_listWidgetNotes_currentItemChanged wasn't call again?
So, if this is the slot that gets called when the selection changes, then instead this slot getting called , create another slot and from there, you call this function..
Now this new slot will have the previous item and from there if the function returns a book instead of void signifying that a cancel is pressed, then you call setCurrentItem again...

How to make a loading screen state transition to game level state?

I am trying to make my basic loading screen transition over to game level screen. So what i wanted to do is, once the loading screen is active (or has appeared onscreen), I want at this point to start loading my game state. What it is doing at the moment is loading everything at the start, and this does take a while.
So currently my project starts off with a main menu. Then when i press enter, its starts the loading screen. I have my manual state change using keypresses like so:
void Game::update()
{
static bool enterPreviouslyPressed = false;
static bool escapePreviousPressed = false;
const Uint8 *keys = SDL_GetKeyboardState(NULL);
if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(GameState))
{
fsm->setState(menuState);
}
else if (keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
fsm->setState(loadingState);
}
else if ((keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed) && typeid(*fsm->getState()) == typeid(LoadScreenState))
{
fsm->setState(gameState);
}
else if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
exit(0);
}
enterPreviouslyPressed = keys[::SDL_SCANCODE_RETURN] != 0;
escapePreviousPressed = keys[::SDL_SCANCODE_ESCAPE] != 0;
fsm->update();
}
I did this to initially does this so i could change states manually to check that everything works. I was wondering if there was an easy(ish) way, like boolean flags for example or another simpler way to do this. I wasn't able find any tutorials online so wondering if someone knows the best solution as to how to do this. I did see a question on here, kindda similar but I wasn't sure if it answered my question as the person did this in threads which I am not familiar with how to implement. Apologies if I dont seem to have the logic correct - so please advise otherwise.
Looks fairly standard, except I would simplify it by keeping two keyboard state variables declared as class variables, like:
const Uint8 *curKeys = SDL_GetKeyboardState(NULL), *prevKeys;
// ...
void Game::update() {
prevKeys = curKeys;
curKeys = = SDL_GetKeyboardState(NULL);
//and so then compare curKeys to prevkeys
//and ditch the booleans
// ...
}

Qt Crash when listWidget item is clicked twice

In my project, I have a listWidget. When the user clicks an item in the list, it loads this:
void BlockSelect::on_blockList_clicked(const QModelIndex &index)
{
QString blockListName;
QString temp_hex;
QString temp_hex2;
int temp_int;
QListWidgetItem *newitem = ui->blockList->currentItem();
blockListName = newitem->text();
temp_hex = blockListName.mid(0, blockListName.indexOf(" "));
if(temp_hex.indexOf(":") == -1)
{
temp_int = temp_hex.toInt();
ui->blockIdIn->setValue(temp_int);
ui->damageIdIn = 0;
}
else
{
temp_hex2 = temp_hex.mid(temp_hex.indexOf(":")+1, temp_hex.length()-(temp_hex.indexOf(":")+1));
temp_hex = temp_hex.mid(0, temp_hex.indexOf(":"));
temp_int = temp_hex.toInt();
ui->blockIdIn->setValue(temp_int);
temp_int = temp_hex2.toInt();
ui->damageIdIn->setValue(temp_int);
}
}
Most of this is just string manipulation. (You don't have need to study this syntax or anything)
My problem is, when the user clicks on another list item quickly (Before this current process is finished) the program crashes. Is there any way to allow fast clicks (multiple processes at once) or maybe an alternative solution?
Thanks for your time :)
I hope that you execute all this code in the GUI thread. If so, then there will be no problem - if your code were correct (it isn't). There is no such thing as the "process" that you mention in your question. The clicks are handled by a slot, and they are invoked from an event handler within the list. This is not supposed to crash, and the clicks will be handled in a serialized fashion - one after another.
Here's the bug: Why do you reset the value of an allocated UI pointer element to zero?
ui->damageIdIn = 0;
This is nonsense. Maybe you mean to ui->damageIdIn->setValue(0) or ui->damageIdIn->hide(). You then proceed to use this zero value in
ui->damageIdIn->setValue(temp_int);
and it crashes.
You may also have bugs in other places in your code.

How does MFC's "Update Command UI" system work?

I'd like to know more about how this system works, specifically when and how the framework actually decides to update a UI element.
My application has a 'tools' system where a single tool can be active at a time. I used the "ON_UPDATE_COMMAND_UI" message to 'check' the tool's icon/button in the UI, which affected both the application menu and the toolbars. Anyway, this was all working great until some point in the last couple of days, when the toolbar icons stopped getting highlighted properly.
I investigated a little and found that the update command was only being received when the icon was actually clicked. What's strange is this is only affecting the toolbars, not the menu, which is still working fine. Even when the buttons in the menu are updated the toolbar icon stays the same.
Obviously I've done something to break it - any ideas?
EDIT:
Never mind. I'd overwritten the Application's OnIdle() method and hadn't called the original base class method - that is, CWinApp::OnIdle() - which I guess is where the update gets called most of the time. This code snippet from https://msdn.microsoft.com/en-us/library/3e077sxt.aspx illustrates:
BOOL CMyApp::OnIdle(LONG lCount)
{
// CWinApp's original method is involved in the update message handling!
// Removing this call will break things
BOOL bMore = CWinApp::OnIdle(lCount);
if (lCount == 0)
{
TRACE(_T("App idle for short period of time\n"));
bMore = TRUE;
}
// ... do work
return bMore;
// return TRUE as long as there are any more idle tasks
}
Here's a good article that kinda explains how to do it. Don't use his code example with WM_KICKIDLE though, instead scroll down to the comments section. There are two code samples that explain how to do it better. I quote:
//Override WM_INITMENUPOPUP
void CDialog::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CDialog::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
// TODO: Add your message handler code here
if(pPopupMenu &&
!bSysMenu)
{
CCmdUI CmdUI;
CmdUI.m_nIndexMax = pPopupMenu->GetMenuItemCount();
for(UINT i = 0; i < CmdUI.m_nIndexMax; i++)
{
CmdUI.m_nIndex = i;
CmdUI.m_nID = pPopupMenu->GetMenuItemID(i);
CmdUI.m_pMenu = pPopupMenu;
// There are two options:
// Option 1. All handlers are in dialog
CmdUI.DoUpdate(this, FALSE);
// Option 2. There are handlers in dialog and controls
/*
CmdUI.DoUpdate( this, FALSE );
// If dialog handler doesn't change state route update
// request to child controls. The last DoUpdate will
// disable menu item with no handler
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_1, FALSE );
...
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_Last, TRUE );
*/
}
}
}
See if this helps - http://msdn.microsoft.com/en-us/library/essk9ab2(v=vs.80).aspx