Has anyone found a good way to save dialog data to a database in CMyDialog::OnOK?
void CMyDialog::OnOK()
{
// If I save my data here, I don't know if DoDataExchange()
// found validation errors.
CDialog::OnOK();
// If I save my data here, EndDialog() has already been called
}
Looking for ideas on how best to structure this. I know the norm is to have the caller save the data as needed but I don't want the dialog to close if I encounter an error saving the data to the database.
It seems like a good solution would be if CDialog::UpdateData() were virtual, but it is not.
Why not just use UpdateData?
The return value:
Nonzero if the operation is successful; otherwise 0. If bSaveAndValidate is TRUE, then a return value of nonzero means that the data is successfully validated.
So:
void CMyDialog::OnOK()
{
if(!UpdateData(TRUE))
{
// There was some error with the validation procedure so don't end the dialog.
return; // Suppress closing dialog
}
// OK to save data
if(!SaveDataToDatabase())
{
// Some error
return;
}
// Data validated Ok and was saved to DB OK, so close
EndDialog(IDOK);
}
Unless I miss understand your question.
So, it seems clear MFC wasn't designed to work this way.
But the simplest solution I found was to modify DoDataExchange() as follows:
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
// DDX and DDV calls go here
if (pDX->m_bSaveAndValidate)
{
if (!SaveData())
pDX->Fail();
}
}
The code above relies on SaveData() displaying an error message and returning false if it encounters any errors.
The result is that the regular validation is performed before I attempt to save my data. And, if the code that saves the data fails, I can still prevent the dialog box from closing via the same technique that the MFC validation methods use. (Namely, by calling pDX->Fail()).
In your comment you say this:
// If I save my data here, EndDialog() has already been called
All that means is that the HWND named m_hWnd has been closed and all the child controls with it. The window is dead, but not your instance of your CVehicleDlg. The member variables associated with the data exchange will have the values from the controls transferred to them. You should be good to go for saving.
Another approach would be to catch OnDestroy in your class instead of OnOK. That way, you get the default processing of OnOk to do the data validation. The window wouldn't be destroyed unless the data validation failed. You'll have to special case OnCancel to set some flag to "not save" when your dialog is destroyed.
Related
I am a newbie writing MFC code, and I have a problem adding a string to a list box. The code shown works OK when I call the AddString object directly, but not when I add it to my own Test object as shown. WHat do I have to do so that AddString still works from inside another object? Maybe some kind of inheritance problem?
Thanks for bearing with me on this newbie question!
Duncan
void CFileProcessorDlg::Test()
{
m_strFullName1.Format(_T("Starting to process files"));
m_Message1.AddString(m_strFullName1); // Add string to list box
}
void CFileProcessorDlg::OnClickedButtonStart()
{
//Duncan's Stuff
// TODO: Add your control notification handler code here
CFileProcessorDlg dlg;
UpdateData(); // Transfer data from controls to variables
//dlg.ProcessFiles(m_InputFile, m_OutputFile); // Actually process the files here
// Problem 5/22/2017 - this works here, but not if I move it into the Test object.
//m_strFullName1.Format(_T("Starting to process files"));
//m_Message1.AddString(m_strFullName1); // Add string to list box
dlg.Test();
}
Terminology first: the entity you're calling "my own Test object" is actually a method.
That out of the way, you're instantiating a(nother) CFileProcessorDlg object (named dlg) from within your CFileProcessorDlg::OnCickedButtonStart() method.
That dlg object is created, you call its Test() method, and then the object goes out of scope at the end of CFileProcessorDlg::OnCickedButtonStart(), so it would not have any useful effect on the object calling object (this).
I think you want to do something like this:
void CFileProcessorDlg::OnClickedButtonStart()
{
//Duncan's Stuff
// TODO: Add your control notification handler code here
UpdateData(); // Transfer data from controls to variables
//dlg.ProcessFiles(m_InputFile, m_OutputFile); // Actually process the files here
// Problem 5/22/2017 - this works here, but not if I move it into the Test object.
//m_strFullName1.Format(_T("Starting to process files"));
//m_Message1.AddString(m_strFullName1); // Add string to list box
Test(); // more explicitly: this->Test()
}
I left your comments for context, but the net change was to not instantiate a new CFileProcessorDlg object.
I need to access the checkbox value in a different program. Check box is initiated in ToolDlg.cpp
DDX_Control(pDX, IDC_CalculateTBA, m_CalculateTBA);
in the oninitdialog initiated like this:
m_CalculateTBA.SetCheck(0);
CalculateAnalyticTBA = false;
void CToolDlg::OnBnClickedCheck3()
{
CalculateAnalyticTBA = m_CalculateTBA.GetCheck();
}
I need checkbox value in SetCal.cpp program. Here is the code i am trying in this program:
CToolDlg dialog;
if( dialog.CalculateAnalyticTBA )
{
Do some thing
}
But the dialog.CalculateAnalyticTBA is always tru even though i don't check the check box.
Plz let me know if you need any other info. Thanx for help.
It looks like you're creating a dialog, and never showing it. So CalculateAnalyticTBA has whatever value you gave it in the constructor of your CToolDlg class (or, if you didn't, whatever value the compiler gave it).
In order for this value to be set you must at least create the dialog so that the Data Exchange code (which invokes the DDX_Control and handles the binding of the checkbox and the variable) has a chance to run.
The correct way is to create and display the CToolDlg dialog and wait for the user to select his choices then only process the choices when the user finally click the "OK" button.
CToolDlg dialog;
// create and display the dialog
if (dialog.DoModal()==IDOK)
{ // user clicked the ok button, now do the work
....
}
I'd like to understand how to do a specific task. I was trying to set up event tables in wxWidgets and ran into an issue with their behaviour.
I set up some code in one class:
void TopRightPanel::OnSelChanged(wxTreeEvent& event)
{
wxTreeItemId item = event.GetItem();
TreeItemData* data = (TreeItemData *) GetItemData(item);
if(data != NULL)
{
particleSystemManager->SetSelectedParticleSystem(data->particleSystem);
}
}
This works fine and has the right values as expected. My problem with this though is that it's self contained and I want the class above it in the hierarchy to read the treeCtrl's action and make changes to all aspects of the U.I. So I tried this:
void Window::OnSelChanged(wxTreeEvent& event)
{
wxTreeItemId item = event.GetItem();
TreeItemData* data = (TreeItemData *) topRightPanel->GetItemData(item);//item.m_pItem.//m_MyTreeCtrl->GetItemData(itemId);*/
if(data != NULL)
{
particleSystemManager.SetSelectedParticleSystem(data->particleSystem);
}
}
Now I get an unhandled exception when topRightPanel->GetItemData(data) is called. The topRightPanel it uses doesn't seem to be updated and seems to be pointing to data before it's enstantuated in the class' constructor. Is there anyway I can get round this?
Edit:
I declare the event table like so:
#if USE_GENERIC_TREECTRL
BEGIN_EVENT_TABLE(TopRightPanel, wxGenericTreeCtrl)
#else
BEGIN_EVENT_TABLE(TopRightPanel, wxTreeCtrl)
#endif
EVT_TREE_SEL_CHANGED(TopRightPanel_Ctrl, Window::OnSelChanged)
END_EVENT_TABLE()
and I then declare the table in the header using DECLARE_EVENT_TABLE.
You must use the same class name in the event table macros that you used in BEGIN_EVENT_TABLE. IOW, your handler must be defined in Window event table and not in TopRightPanel event table. As tree events are wxCommandEvents, they are propagated upwards to the parent so if Window contains the tree control, this will work. Otherwise, e.g. if they are siblings, you would have to use Connect() as indicated in another answer.
(Re)reading the event handling overview would be highly recommended.
You don't show how do you connect this event handler but the problem is almost surely there and not in the code you show. If you're using Connect(), make sure that you pass the pointer to Window object as its last argument, otherwise you end up calling a method of Window class on TopRightPanel object with the unsurprisingly catastrophic consequences.
If you're sure that you do call the method on the right object, then the only other explanation I see is that you don't initialize topRightPanel before the first event of this type is generated. If this is the case, the simplest solution is to initialize the pointer to NULL and set it correctly at the end of the initialization code. And, of course, check for the pointer being non-NULL in the event handler.
I'm having a strange problem with wxWidgets. I have the following code
MyFrame::OnDoSomeLongThing(...) {
progScreen = new wxProgressDialog(text,text,number,this,wxPD_AUTO_HIDE); // wxProgressDialog *progScreen is class member
doPartOfThing() // calls the update method at the end of it
....
doLastPartOfThing() // again calls update method that pushes value to 100/100
progScreen->Destroy();
}
MyFrame::update() {
progScreen->Update(newValue);
}
Now here's the thing. I can literally comment out the lines relating to progScreen, just let the process go without using a progress dialog, after all is said and done, my apps exits gracefully when I close the main window.
However, just the use of the progress dialog is somehow extending the life of the application. I've tried Destroy(), I've tried simply 'delete progScreen', and both, every time: I'll close the main frame, the process keeps running, and at some point exits with some astronomical number. The only thing I could think might be relevant, is that the doPartsOfThings methods may call boost::this_thread::sleep, because it involves waiting and whatnot down in my model class. But this shouldn't have anything to do with my problem. Or maybe it does... EDIT: I do want to emphasize that progScreen->Update() IS being called from the main (GUI) thread.
So I ask, am I using a wxProgressDialog correctly? If not, how should it be used?
Thanks for your help!
EDIT:
Well... it turns out that removing wxPD_AUTO_HIDE fixed the problem. I'm still not quite sure what the problem is, but the dialog even still behaves as before. App closes as expected.
I think that you need to override the wxApp method that closes the application so that it closes the wxProgressDialog object before it quits.
wxApp::OnExit
virtual int OnExit()
Override this member function for any processing which needs to be
done as the application is about to exit. OnExit is called after
destroying all application windows and controls, but before wxWidgets
cleanup. Note that it is not called at all if OnInit failed.
The return value of this function is currently ignored, return the
same value as returned by the base class method if you override it.
You will need something like, assuming progScreen is a public attribute of your frame
int myApp::OnExit()
{
(MyFrame*)(GetTopWindow())->progScreen->Destroy()
return wxApp::OnExit();
}
So I thought this would be pretty simple, but I forgot it's MFC. Instead of registering a notification listener for data model changes that would possibly require a GUI update on each individual control I figure why not register it once and then send a message to all the open dock panes and allow them to update their controls as needed on their own terms for efficiency.
My callback function for handling the notification from the server looks something like this:
void CMainFrame::ChangeCallback(uint32_t nNewVersion, const std::vector<uint32_t>& anChangedObjectTypes)
{
CObList panes;
GetDockingManager()->GetPaneList(panes); // assert failure
if (!panes.IsEmpty())
{
POSITION pos = panes.GetHeadPosition();
while (pos)
{
CDockablePane* pPane = dynamic_cast<CDockablePane*>(panes.GetNext(pos));
if (pPane)
pPane->PostMessage(DM_REFRESH, nNewVersion);
}
}
}
The error I am getting is an assertion failure on line 926 of wincore.cpp
CHandleMap* pMap = afxMapHWND();
ASSERT(pMap != NULL); // right here
There is a comment below this saying this can happen if you pass controls across threads however this is a single threaded MFC application and this is all being done from the main frame.
Does anyone know what else can cause this?
If there is another way to go about sending a message to all the open CDockablePane derived windows in MFC that works as well ...
Here's the obvious workaround that I didn't want to have to do but after hours of debugging and no response here I guess this is a viable answer:
I added std::vector<CDockPane*> m_dockList; to the members of CMainFrame
Now after each call to AddPane in various places that can create and open new dock panes I make a subsequent call to push_back and then I override CDockablePane::OnClose like so:
CMainFrame* pMainFrame = reinterpret_cast<CMainFrame*>(AfxGetMainWnd());
if (pMainFrame)
{
std::vector<CDockPane*>::const_iterator found(
std::find(pMainFrame->DockList()->begin(), pMainFrame->DockList()->end(), this));
if (found != pMainFrame->DockList()->end())
pMainFrame->DockList()->erase(found);
}
CDockablePane::OnClose();
Now this list will only contain pointers to open dock panes which allows me to handle the event notification in my callback and simply do a for loop and PostMessage to each.