How to 'properly close MFC Modeless Dialogs and fixing resource leak' - visual-c++

I've created an MFC Application, with a Base Dialog (derived from CDialog class), a Setting Dialog (derived from CBaseDlg and an App Dialog (also derived from CBaseDlg). Then I created a class called CScrMng (aka Screen Manager) that hold the ShowDialog() function (to Create and Show these dialogs).
The basic idea is ScrMng will manage all of my Modeless Dialogs, and anytime I want to open a dialog, I just need to CScrMng::ShowDialog() in the BaseDlg.cpp, and the dialog will display.
This approach has caused resources to leak here and there. I've done a bit of research about overriding the PostNcDestroy(), but I don't have a clear idea of where to call it.
What function should I use to properly close these modeless dialogs?
I want to open the Setting Dialog from Base Dialog, then when I click on the Cancel button, it should return me to the Base Dialog screen so that I can open another Dialog.
Right now I'm using EndDialog(). I know it's wrong, but calling DestroyWindow() will immediately exit the program, which is not what I want.
Source code
MFCApplication.cpp
#include...
BEGIN_MESSAGE_MAP(CMFCApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
CMFCApp::CMFCApp()
{
// support Restart Manager
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
}
CMFCApp theApp;
BOOL CMFCApp::InitInstance()
{
...
CWinApp::InitInstance();
CShellManager *pShellManager = new CShellManager;
CScrMng::GetInstance()->ShowDialog(IDD_MAINDLG);
SetRegistryKey(_T("Local Applications"));
if (pShellManager != NULL)
{
delete pShellManager;
}
return TRUE;
}
CScrMng.cpp
#include...
CScrMng* CScrMng::m_pInstance = NULL;
CScrMng* CScrMng::GetInstance(){
if (m_pInstance == NULL)
m_pInstance = new CScrMng();
return m_pInstance;
}
CScrMng::CScrMng(){}
void CScrMng::ShowDialog(int ID)
{
CMainDlg* m_pDlg = NULL;
switch (ID)
{
case IDD_MAINDLG:
m_pDlg = new CMainDlg();
theApp.m_pMainWnd = m_pDlg;
m_pDlg->Create(IDD_MAINDLG);
m_pDlg->ShowWindow(SW_SHOW);
m_pDlg->UpdateWindow();
break;
case ...
break;
case IDD_SETTINGDLG:
m_pDlg = new CSettingDlg();
m_pDlg->Create(ID,NULL);
m_pDlg->ShowWindow(SW_SHOW);
m_pDlg->UpdateWindow();
break;
}
CMainDlg.cpp
#include...
CMainDlg::CMainDlg(CWnd* pParent /*=NULL*/)
: CDialog(CMainDlg::IDD, pParent) {m_hIcon = AfxGetApp()-> LoadIcon(IDR_MAINFRAME);}
void CMainDlg::DoDataExchange(CDataExchange* pDX) {...}
void CMainDlg::PostNcDestroy() //Added these
{
CDialog::PostNcDestroy();
delete this;
}
BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
...
END_MESSAGE_MAP()
BOOL CMainDlg::OnInitDialog() {
CDialog::OnInitDialog();
SetIcon(m_hIcon, FALSE);
return TRUE;
}
void CMainDlg::OnPaint() {...}
void CMainDlg::OnBnClickedOpenAppdlg()
{
CScrMng::GetInstance()->ShowDialog(IDD_APPDLG);
}
void CMainDlg::OnBnClickedOpenSettingdlg()
{
CScrMng::GetInstance()->ShowDialog(IDD_SETTINGDLG);
}
void CMainDlg::OnBnClickedExit()
{
DestroyWindow(); //replaced CDialog::OnCancel() with this.
}
Update: After changing the code in the SettingDlg.cpp, i encountered a Debug Assertion Failed! problem :
void CWnd::MoveWindow(int x, int y, int nWidth, int nHeight, BOOL bRepaint)
{
ASSERT(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL)); //Breakpoint triggered
if (m_pCtrlSite == NULL)
::MoveWindow(m_hWnd, x, y, nWidth, nHeight, bRepaint);
else
m_pCtrlSite->MoveWindow(x, y, nWidth, nHeight);
}
Here are what i changed in the .cpp file:
SettingDlg.cpp
void CSettingDlg::PostNcDestroy()
{
CMainDlg::PostNcDestroy();
}
void CSettingDlg::OnBnClickedSettingcancel()
{
DestroyWindow(); //Using destroyWindow rather than EndDialog();
}

Close & Delete a modeless Dialogs.
A proper Way is: Override PostNcDestroy, OnOk() and OnCancel() for the Modeless Dialogs
void CBaseDlg::PostNcDestroy()
{
CDialog::PostNcDestroy();
delete this;
}
.
void CBaseDlg::OnOk()
{
if(!UpdateData(TRUE))
return;
DestroyWindow();
}
.
void CBaseDlg::OnCancel()
{
DestroyWindow();
}

Related

An object reference is required for the non-static field method or property/field initializer cannot reference the non-static field method or property

I was making a program and I stumbled across this two-in-one problem, where the first problem leads to the other. I have not yet found a question where someone had both problems leading into eachother. I'm still learing, and have learned a lot from other problems I had, but I can't find a solution to this problem.
It has to do with threading. I want to make a thread, that can place something in a rich textbox every second or so, while I can still press buttons to start and stop it. But to make a function that a thread can run, I need to make the function static. Otherwise I'll get the error "A field initializer cannot reference the non-static field, method, or property". But when a function is static, it cannot acces any of the created items, like richTextBox1. Because if I try to change it's text, I get the error "Error 1 An object reference is required for the non-static field, method, or property". And if I fix this by removing static, the thread will not work.
I made a demo program that is smaller than the full one, but has the same problem. Button1 is the button to start the thread, Button2 is the one to stop it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace threading_non_static_problem_demo
{
public partial class Form1 : Form
{
static Thread thr = new Thread(new ThreadStart(demofunc));
int checkthr = 0; //int to check if the thread has been running before (I like to do things like this)
int ifthrrun = 0; //int to check if the thread is running
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
}
static void demofunc()
{
while (true)
{
Thread.Sleep(1000);
richTextBox1.Text = richTextBox1.Text + "text added"; // <-- here's the problem
MessageBox.Show("tried to add text"); // you can use this messagebox to check if the thread is working correctly
}
}
private void button1_Click(object sender, EventArgs e)
{
if (checkthr == 0) // check if the thread has run before, 0 is no, 1 is yes, and then start or resume it
{
thr.Start();
ifthrrun = 1;
button2.Enabled = true;
button1.Enabled = false;
}
else if (checkthr == 1)
{
thr.Resume();
ifthrrun = 1;
button2.Enabled = true;
button1.Enabled = false;
}
}
private void button2_Click(object sender, EventArgs e)
{
thr.Suspend();
checkthr = 1;
ifthrrun = 0;
button2.Enabled = false;
button1.Enabled = true;
}
protected override void OnFormClosing(FormClosingEventArgs e) // if the program is closing, check the thread's state and act accordingly
{
if (ifthrrun == 0)
{
if (checkthr == 1)
{
thr.Resume();
thr.Abort();
}
else if (checkthr == 0)
{
}
}
else if (ifthrrun == 1)
{
thr.Abort();
}
}
}
}
To use this code just creat a forms application, add two buttons, and a rich text box, it should work.
Thank you in advance for you answers.
But to make a function that a thread can run, I need to make the
function static.
Get rid of the static declarations and move initialization of your "thr" variable to the constructor like this:
Thread thr;
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
thr = new Thread(new ThreadStart(demofunc));
Control.CheckForIllegalCrossThreadCalls = false;
}
private void demofunc()
{
while (true)
{
Thread.Sleep(1000);
richTextBox1.Text = richTextBox1.Text + "text added"; // <-- problem "solved" by disabling Control.CheckForIllegalCrossThreadCalls
}
}
But ignore the above "fix" because using Suspend()/Resume() is not recommended.
See Pausing and Resuming Threads:
Important
Starting with the .NET Framework version 2.0, the Thread.Suspend and
Thread.Resume methods are marked obsolete and will be removed in a
future release.
The Thread.Suspend and Thread.Resume methods are not
generally useful for applications and should not be confused with
synchronization mechanisms. Because Thread.Suspend and Thread.Resume
do not rely on the cooperation of the thread being controlled, they
are highly intrusive and can result in serious application problems
like deadlocks (for example, if you suspend a thread that holds a
resource that another thread will need).
One way to be able to pause/resume your loop would be to use a ManualResetEvent like this:
Thread thr;
ManualResetEvent mre = new ManualResetEvent(false);
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
thr = new Thread(new ThreadStart(demofunc));
}
private void demofunc()
{
while (!this.IsDisposed && !this.Disposing)
{
Thread.Sleep(1000);
if (!this.IsDisposed && !this.Disposing)
{
this.Invoke((MethodInvoker)delegate {
richTextBox1.AppendText("text added");
});
}
mre.WaitOne();
}
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
mre.Set();
if (!thr.IsAlive)
{
thr.Start();
}
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
mre.Reset();
button1.Enabled = true;
}
protected override void OnFormClosing(FormClosingEventArgs e) // if the program is closing, check the thread's state and act accordingly
{
mre.Set(); // make sure the loop continues so it can drop out
}
}

Crashing while executing GetParent(). Closing of a modeless dialog box

I am creating a modeless dialog box. The dialog box is called from the menu item of main frame window.
MainFrm.h
CModeless* modeless;
bool modelessDlgOpen;
MainFrm.cpp
void CMainFrame::OnDatabaseMLdlg()
{
// TODO: Add your command handler code here
if (modelessDlgOpen == TRUE)
return;
modelessDlgOpen = TRUE;
modeless = new CModeless(this);
//modeless->Create(IDD_MLDLG, GetDesktopWindow());
modeless->Create(IDD_MLDLG, this);
mbPoll->ShowWindow(SW_SHOW);
}
When menu item is clicked, OnDatabaseMLdlg() function is called and a modeless dialog box with resource ID IDD_MLDLG appears.
The issue is while closing the modeless dialog box.
I am not able to find out the correct method to have a clean closure / destroy of this modeless dialog box. Upon clicking the cross button in right-top corner, which message gets
generated?
My current code which I have tried is as follows. (producing code related only to the closure of the dialog box)
MLDLG.h
#pragma once
#define WM_MLDLG_CLOSED (WM_USER + 555)
// CModeless dialog
class CModeless : public CDialog
{
DECLARE_DYNAMIC(CModeless)
public:
CModeless(CWnd* pParent = NULL); // standard constructor
virtual ~CModeless();
// Dialog Data
enum { IDD = IDD_MLDLG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
virtual BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);
afx_msg void OnNcDestroy();
virtual void PostNcDestroy();
CWnd* mParent;
afx_msg void OnBnClickedCancel();
};
MLDLG.cpp
void CModeless::OnNcDestroy()
{
CDialog::OnNcDestroy();
// TODO: Add your message handler code here
}
void CModeless::PostNcDestroy()
{
CDialog::PostNcDestroy();
GetParent()->PostMessage(WM_MLDLG_CLOSED,0,0); // **CRASHES HERE**
delete this;
}
void CModeless::OnBnClickedCancel()
{
// TODO: Add your control notification handler code here
//CDialog::OnCancel();
DestroyWindow();
}
Not able to understand what am I doing wrong or what am I missing?
I can provide additional details in case required.
Thanks in advance.
EDIT-20130612: Additional information:
My constructor is as follows:
CModeless::CModeless(CWnd* pParent /*=NULL*/)
: CDialog(CModeless::IDD, pParent)
{
mParent = pParent;
if (mParent == NULL)
{
MessageBox(L"mParent is NULL");
}
else
{
MessageBox(L"mParent is not NULL");
}
}
Here, I have verified that mParent is not NULL.
PostNCDestroy() is called VERY late and most of the useful state of the MFC window is not valid at that point. GetParent() is probably returning NULL, which will cause a crash the way you are using it.
Try moving the PostMessage call to OnDestroy() before calling the base class implementation there.
Another option is to cache the parent's hWnd and call ::PostMessage() on that hWnd;

In J2ME, is that possible to operate an Alert dialog box with Yes & NO command?

I have created an Alert dialog box in my J2ME app to alert user when user press exit button to terminate an app and ask user confirmation to exit from app with yes and no command.
When user press Yes button app will terminate and when user press No button app will return to its Main form. To do this I developed a code from scratch which are as follows:
public class CustomAlert extends MIDlet implements CommandListener
{
Alert ExitAlrt;
Display d;
Command MainListSelect, Exit, YesCmdAlrt, NoCmdAlrt;
List MainList;
public CustomAlert()
{
d = Display.getDisplay(this);
//Initialization of commands
MainListSelect = new Command("Select", Command.SCREEN, 1);
Exit = new Command("Exit", Command.STOP, 2);
//Initialization of lists
MainList = new List("Menu", List.IMPLICIT);
//Adding command to lists
MainList.addCommand(MainListSelect);
MainList.addCommand(Exit);
MainList.setCommandListener(this);
//Appending the content of lists
MainList.append("Settings",null);
}
protected void startApp()
{
MainList.setSelectedIndex(0, true);
d.setCurrent(MainList);
}
protected void pauseApp() { }
protected void destroyApp(boolean unconditional){}
//This method handle commands which operate list that is Select & Exit
public void commandAction(Command cmd,Displayable dispable)
{
if(cmd == MainListSelect)
{
int slctindx = MainList.getSelectedIndex();
if(slctindx == 0)
{}
else if(slctindx == 1)
{}
}
if(cmd == Exit)
{
ExitAlrt = new Alert("Application Alert","Are you sure you want to exit?",null, AlertType.WARNING);
YesCmdAlrt = new Command("Yes", Command.EXIT,1);
ExitAlrt.addCommand(YesCmdAlrt);
NoCmdAlrt = new Command("No", Command.SCREEN,2);
ExitAlrt.addCommand(NoCmdAlrt);
d.setCurrent(ExitAlrt);
}
}
//This Code handle Commands present on Alert dialog box.
public void commandAction(Command cmd) /
{
ExitAlrt.setCommandListener(this);
if(cmd == NoCmdAlrt)
{
d.setCurrent(MainList);
}
else if(cmd == YesCmdAlrt)
{
destroyApp(true);
notifyDestroyed();
}
}
}
In above code problem is when I click on Exit button, Alert box appears and when I press Yes button to terminate an app it again redirect to me on Main List of an app. I did lot of placements in code but problem remain constant.
What is solution for this?
ExitAlert in the posted code snippet lacks a command listener because you didn't invoke setcommandListener for it. As a result, instead of expected exit, default command action happens which is to simply dismiss the alert, as explained in API javadocs:
If the user invokes a Command and the default listener is present, the default listener ignores the Command and implements the automatic-advance behavior.
Note you might think that ExitAlrt.setCommandListener(this) inside commandAction(Command cmd) method does the trick for you but this is not so, because this method is not invoked in between creation of the ExitAlrt instance and displaying it.
To get the desired behavior, implement and set an appropriate command listener for ExitAlrt prior to invoking setCurrent.
// ...
if(cmd == Exit)
{
System.out.println("Exit command invoked"); // log message for debugging
Alert ExitAlrt = new Alert("Application Alert",
"Are you sure you want to exit?", null, AlertType.WARNING);
ExitAlrt.addCommand(new Command("Yes", Command.EXIT, 1));
ExitAlrt.addCommand(new Command("No", Command.SCREEN, 2));
// --> set command listener for ExitAlrt prior to invoking setCurrent
ExitAlrt.setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
System.out.println("command: [" + c.getCommandLabel()
+ "] at screen: [" + d.getTitle() + "]"); // for debugging
if (c.getCommandType() != Command.EXIT) {
System.out.println("Exit cancelled"); // for debugging
d.setCurrent(MainList);
return;
}
System.out.println("Exit confirmed"); // for debugging
destroyApp(true);
notifyDestroyed();
}
});
d.setCurrent(ExitAlrt);
}
// ...
For simplicity, above code snippet uses System.out.println for logging. If needed, refer to another SO question for an explanation and example of how this could be done in a more practical way.

Preventing TabControl selection in Silverlight

Is there any way to prevent the change of a tab in TabControl in Silverlight 4?
A simple case is when I've got a form with some data, and I want to ask the user if he/she wants to save this data before actually changing the tab.
I've seen code examples on how to do this in WPF, but not in Silverlight.
What can I do to stop the tab from changing?
Bind SelectedIndex to a property on your data context.
<sdk:TabControl SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}">
<sdk:TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</sdk:TabItem>
<sdk:TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</sdk:TabItem>
</sdk:TabControl>
In the SET accessor, write your code to check whether the user really wants to do what they're trying to do.
public class Context : INotifyPropertyChanged
{
int _SelectedIndex = 0;
public int SelectedIndex
{
get
{
return _SelectedIndex;
}
set
{
MessageBoxResult result = MessageBox.Show("Do you want to save?", "Really?", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
_SelectedIndex = value;
}
RaisePropertyChanged("SelectedIndex");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Net effect is, if the user chooses 'cancel' on the dialogue the private variable is never changed - the PropertyChanged event fires, rebinding the selected index to the existing value.
Hope this is what you were trying to accomplish.
UPDATE (11/10/2012) - Alternate method (possibly for SL5?). Write code behind to tie into SelectionChanged event of your TabControl, reset the tab control's selected item property based on your test.
private void TabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
{
MessageBoxResult result = MessageBox.Show("Do you want to save?", "Really?", MessageBoxButton.OKCancel);
if (result != MessageBoxResult.OK)
{
((TabControl)sender).SelectionChanged -= new SelectionChangedEventHandler(TabControl_SelectionChanged);
((TabControl)sender).SelectedItem = e.RemovedItems[0];
((TabControl)sender).SelectionChanged += new SelectionChangedEventHandler(TabControl_SelectionChanged);
}
}
}

Blackberry - how to create a layout like sliding drawer android

Does anyone know how to create layout component in blackberry that behave like android's sliding drawer?
thx.
Haven't actually done it. But I think the biggest difficulty here is the Sliding animation part and the visibility IMHO. You should first work on the animation for the sliding effect. Then on the Manager itself.
Touch devices come with another difficulty... you have to program the slider touch event so that it follows the gesture.
hey its really a brilliant idea to have such layouts for blackberry. it is quite challenging to get it done. we have to play with custom layouts.
first thing requied is the knowledge of customization of managers. I believe we can also use pop up screen to slide it up. manager on top of an pop up screen.
secondly, the Gusture api for side scrolling.
all the best buddy.
Please check the below source code in which i created a manager class. Just add whatever you want to slide to that manager and use it. Change according to your requirement.
package mypackage;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.LabelField;
public class SlidingDrawer extends Manager {
final static int STATE_VIEW = 1;
final static int STATE_HIDE = STATE_VIEW+1;
final static int STATE_SLIDE_DOWN = STATE_HIDE+1;
final static int STATE_SLIDE_UP = STATE_SLIDE_DOWN+1;
final static int MAX_NO_OF_STATES = STATE_SLIDE_UP+1;
static int CURRENT_STATE = STATE_VIEW;
int i = 0;
LabelField _lbl_hero;
public SlidingDrawer(long arg0) {
super(arg0);
// TODO Auto-generated constructor stub
init();
}
private void init()
{
_lbl_hero = new LabelField("Hero testing Every thign....");
this.add(_lbl_hero);
}
protected void paint(Graphics graphics) {
// TODO Auto-generated method stub
switch(CURRENT_STATE)
{
case STATE_VIEW:
super.paint(graphics);
break;
case STATE_HIDE:
break;
case STATE_SLIDE_DOWN:
super.paint(graphics);
if(i<this.getHeight())
{
this.getField(0).setPadding(i++, 0, 0, 0);
invalidate();
}
else
{
CURRENT_STATE = STATE_HIDE;
}
break;
case STATE_SLIDE_UP:
super.paint(graphics);
if( i > 0 )
{
this.getField(0).setPadding(i--, 0, 0, 0);
invalidate();
}
else
{
CURRENT_STATE = STATE_VIEW;
}
break;
}
}
public void setState(int state)
{
if(state < MAX_NO_OF_STATES)
{
CURRENT_STATE = state;
}
else
Dialog.alert("Invalid State....");
}
protected boolean touchEvent(TouchEvent message) {
// TODO Auto-generated method stub
if(CURRENT_STATE == STATE_VIEW)
{
i=0;
CURRENT_STATE = STATE_SLIDE_DOWN;
invalidate();
}
else if(CURRENT_STATE == STATE_HIDE)
{
// i = this.getField(0).getContentRect().y;
CURRENT_STATE = STATE_SLIDE_UP;
invalidate();
}
return super.touchEvent(message);
}
protected void sublayout(int maxWidth, int maxHeight) {
// TODO Auto-generated method stub
setExtent(360, 100);//Width and Height of the Childs
}
}
Please change this code according to your requirement and use it.

Resources