Events wxWidgets - c++

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.

Related

Problems saving dialog data in OnOK

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.

MFC CView (CFormView) destruction crash

As per this stackoverflow question:
What is the correct way to programmatically quit an MFC application?
I am using AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0); to exit an MFC program. (SDI, CFrameWnd containing a CSplitterWnd with two CFormViews)
As expected, this calls DestroyWindow().
The problem I am facing is that after the derived CFormView destruction, as per MSDN:
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, but m_hWnd will be NULL. [MSDN]
Now the CView destructor is called and at the point it does the
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
it fails on the following assert: ASSERT(::IsWindow(pView->m_hWnd));
I checked and the m_hWnd is already set to NULL in the derived CView destructor called just before.
What am I doing wrong ?
EDIT:
Here is a chart illustrating why I want to send a WM_CLOSE message and not a WM_QUIT.
I think the answer lays in this MSDN Technical Note, but I can't figure it out.
EDIT 2:
The order that things get called:
1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
2- Derived CFrameWnd::OnClose()
3- CFrameWnd::OnClose()
which calls CWinApp::CloseAllDocuments(BOOL bEndSession);
which calls CDocManager::CloseAllDocuments(BOOL bEndSession)
which calls CDocTemplate::CloseAllDocuments(BOOL)
which calls CDocument::OnCloseDocument()
Now, in this function
while (!m_viewList.IsEmpty())
{
// get frame attached to the view
CView* pView = (CView*)m_viewList.GetHead();
ASSERT_VALID(pView);
CFrameWnd* pFrame = pView->EnsureParentFrame();
// and close it
PreCloseFrame(pFrame);
pFrame->DestroyWindow();
// will destroy the view as well
}
So we see that CWnd::DestroyWindow() is called, so:
4- Derived CFormView destructor
5- CScrollView::~CScrollView()
6- CView::~CView()
which calls CDocument::RemoveView(CView* pView)
which calls CDocument::OnChangedViewList()
which calls CDocument::UpdateFrameCounts()
Which crashes here: ASSERT(::IsWindow(pView->m_hWnd));
because pView->m_hWnd is NULL...
EDIT 3:
I figured out what the problem was:
The destructor of the first view was deleting an uninitialized pointer, which is UB. This was making the destructor hang and never complete.
Usually, the destructor of the second view is only called upon completion of the first one. But in this case it was still being executed although the first one never completed.
Since the first view base class destructors were never called, this function was never called for the first view:
void CDocument::RemoveView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == this); // must be attached to us
m_viewList.RemoveAt(m_viewList.Find(pView));
pView->m_pDocument = NULL;
OnChangedViewList(); // must be the last thing done to the document
}
Where we can see that the view is removed from the m_viewList.
This means that when the second view destructor completes, in:
void CDocument::UpdateFrameCounts()
// assumes 1 doc per frame
{
// walk all frames of views (mark and sweep approach)
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
...
The pos is supposed to be NULL, but it is not. Which lead to the crash.
I think the way you are closing the frame is not the issue there.
My guess is that you destroy one of the views by hand whereas you should let MFC delete them (you probably called DestroyWindow on one of them)
Call ::PostQuitMessage(0); to close the app.
The problem was resolved, see EDIT 3 in the question for the solution.

Firemonkey: Getting parent form of control

I have a custom control which needs to get access to the height of the main form it is on. Since it is common for this control to be nested in a series of panels, I have written this code to try and get me to the main form:
TControl * control = this;
while( control->HasParent() )
{
control = control->ParentControl;
ShowMessage( control->Name );
}
Using the ShowMessage statement to track my progress, as I step through the code I get all the way up to "BasePanel" which in this case is the last control up the ladder before the "MainForm." However, when the call to ShowMessage happens for what should be the "MainForm" I get an access violation.
Is there some reason I am unable to access the main form of a control this way? Is there a better way to access a control's main form?
You are not checking if ParentControl returns a NULL pointer before reading its Name. When HasParent() returns true, ParentControl is NOT guaranteed to be valid. Case in point - TForm is NOT a TControl descendant in FireMonkey, so it cannot be returned by ParentControl.
The purpose of HasParent() is to report whether the component has a parent or not. TFmxObject overrides HasParent() to report whether the TFmxObject.Parent property is NULL, and overrides GetParentComponent() to return an appropriate TComponent for that parent. TFmxObject.Parent returns a TFmxObject, as parent/child relationships do not have to be visual in FireMonkey like they do in VCL, so Parent and GetParentComponent() can actually return different objects at times.
You should be using GetParentComponent() instead of ParentControl, as the documentation says:
Call HasParent to determine whether a specific component has a parent.
Derived classes override this method to implement proper handling for parenting.
Use GetParentComponent to retrieve the component reference.
For example:
TComponent * comp = this;
while( comp->HasParent() )
{
comp = comp->GetParentComponent();
ShowMessage( comp->Name );
}
However, if your intent is to find the parent TForm specifically, use your control's Root property instead:
TCommonCustomForm *form = dynamic_cast<TCommonCustomForm*>(this->Root->GetObject());

Custom events in Qt

I am experimenting with custom events in Qt Creator. I am currently examining this example code on another site:
bool MyClass::event(QEvent* e)
{
if (e && e->type() == MyCustomEventType) {
MyCustomEvent* ce = dynamic_cast<MyCustomEventType*>(e);
return handleCustomEvent(ce);
}
// very important: still handle all the other Qt events!
return QObject::event(e);
}
The conditional statement checks if the event passed is the custom event, then it executes code that it wants to happen when the event occurs. What I do not understand is return handleCustomEvent(e) (what is this function supposed to do and where is it supposed to be declared?) and what return QObject::event(e) does. From what I read on the Qt documentation, the only thing this function does is return whether the event's function (is this handleCustomEvent?) is "recognized and processed". Is this supposed to handle all other events in the loop?
handleCustomEvent() is the method you need to implement in your class MyClass which will process your Custom Event MyCustomEventType.
If it's not your custom event, the last line return QObject::event(e); will be called to handle other events type.
So the method in your snippet bool MyClass::event(QEvent* e), is acting like a routing code, to decide where to send the event for processing, and does not actually process the events.
Once decided that 'e' is of type MyCustomEventType - it invokes handleCustomEvent() which will contain your code to handle this event type.
If not - the last line calls QObject::event() to process it instead. This will handle all other remaining types of events.
So, no, you need not worry about handling other events, unless you want to.
So, you'd declare the handleCustomEvent() in MyClass and implement it as well.
Something like:
class MyClass {
...
...
public:
bool handleCustomEvent(MyCustomEventType* e);
...
...
};
In the implementation you may have the logic as you require - to actually do the processing for your custom-event type MyCustomEventType.

Weird bug in Qt application

In my application, I have my re-implemented QGraphicsView checking for a mouseReleaseEvent(), and then telling the item at the position the mouse is at to handle the event.
The QGraphicsItem for my view is made up of two other QGraphicsItems, and I check which one of the two is being clicked on (or rather having the button released on), and handle the respective events.
In my Widget's constructor, I set one of the items as selected by default, using the same methods I used when the items detect a release.
When I debugged, I found that for the LabelItem, select is called without a problem from the constructor (and the result is clear when I first start the application). But, when I click on the items, the application terminates. I saw that I was getting into the select function, but not leaving it. So the problem is here.
Which is very weird, because the select function is just a single line setter.
void LabelItem::select()
{
selected = true;
}
This is the mouseReleaseEvent;
void LayerView::mouseReleaseEvent(QMouseEvent *event)
{
LayerItem *l;
if(event->button() == Qt::LeftButton)
{
l = (LayerItem *) itemAt(event->pos());
if(l->inLabel(event->pos()))
{ //No problem upto this point, if label is clicked on
l->setSelection(true); //in setSelection, I call select() or unselect() of LabelItem,
//which is a child of LayerItem, and the problem is there.
//In the constructor for my main widget, I use setSelection
//for the bottom most LayerItem, and have no issues.
emit selected(l->getId());
}
else if(l->inCheckBox(event->pos()))
{
bool t = l->toggleCheckState();
emit toggled(l->getId(), t);
}
}
}
When I commented the line out in the function, I had no errors. I have not debugged for the other QGraphicsItem, CheckBoxItem, but the application terminates for its events as well. I think the problem might be related, so I'm concentrating on select, for now.
I have absolutely no clue as to what could have caused this and why this is happening. From my past experience, I'm pretty sure it's something simple which I'm stupidly not thinking of, but I can't figure out what.
Help would really be appreciated.
If the LabelItem is on top of the LayerItem, itemAt will most likely return the LabelItem because it is the topmost item under the mouse. Unless the LabelItem is set to not accept any mouse button with l->setAcceptedMouseButtons(0).
Try to use qgraphicsitem_cast to test the type of the item. Each derived class must redefine QGraphicsItem::type() to return a distinct value for the cast function to be able to identify the type.
You also could handle the clicks in the items themselves by redefining their QGraphicsItem::mouseReleaseEvent() method, it would remove the need for the evil cast, but you have to remove the function LayerView::mouseReleaseEvent() or at least recall the base class implementation, QGraphicsView::mouseReleaseEvent(), to allow the item(s) to receive the event.
I have seen these odd behaviours: It was mostly binary incompatibility - the c++ side looks correct, and the crash just does not make sense. As you stated: In your code the "selected" variable cannot be the cause. Do you might have changed the declaration and forgot the recompile all linked objects. Just clean and recompile all object files. Worked for me in 99% of the cases.