Conditionally stopping a CPropertySheet from closing from the page OnOK button handler - visual-c++

I have just encountered an issue with CPropertyPage.
I have been trying to use the OnOK handler to do some validation:
void CCalendarSettingsGooglePage::OnOK()
{
bool bHandle = false;
UpdateData(TRUE);
// AJT v20.2.0 — We need to pass "true" so that the error message will display!
if (ValidSettings(true))
{
bHandle = true;
SaveSettings();
}
if (bHandle)
CMFCPropertyPage::OnOK();
}
The problem is, the sheet still closes down. I had hoped that preventing the CMFCPropertyPage::OnOK would have stopped the sheet closing. But it doesn't.
I understand from here that the sheet's OnOK is making a EndDialog(IDOK) call. But i don't want to complicate my sheet. The testing is here in this page. so I need a was for the sheet to know if it should close or not when user clicks the OK button.

You need to override the OnCommand handler of your property page's parent property sheet class and intercept the clicks for the IDOK command (which will be given in the wParam parameter). If you do not call the base class OnCommand but still return TRUE to indicate that your have handled the command, then the property sheet will not close:
BOOL MyPropertySheet::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (wParam == IDOK) { // OK button clicked...
if (!ValidSettings(true)) return TRUE; // NOT valid, prevent further processing.
}
// You can also intercept the "Apply" command by testing for ID_APPLY_NOW
// Everything is OK, so continue processing ...
return CMFCPropertySheet::OnCommand(wParam, lParam);
}
Note that I have assumed your parent is derived from CMFCPropertySheet, but the same works for the 'older' CPropertySheet.

Related

Cannot trigger cancel button action after processing results returned

Within the Acumatica 19.201.0070 framework I have created a custom processing page that utilizes PXFilteredProcessing with the old style processing UI public override bool IsProcessing => false; I have defined a cancel button (below) that will clear the graph and set some values of the processing filter.
public PXCancel<NPMasterSubGeneratorFilter> Cancel;
[PXCancelButton()]
protected virtual IEnumerable cancel(PXAdapter adapter)
{
NPMasterSubGeneratorFilter row = Filter.Current;
if (row != null)
{
this.Clear();
Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentID>(Filter.Current, row.SegmentID);
if (!(row.NewSegment ?? false)) Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentValue>(Filter.Current, row.SegmentValue);
}
return adapter.Get();
}
This works perfectly fine except for a single use case, after processing results are shown if the user then presses the cancel button the corresponding action is never hit. ( My fellow office devs state that core Acumatica processing pages seem to operate the same. )
Setting of the processing delegate is within the filter RowSelected event.
GeneratedSubs.SetProcessDelegate(list => CreateSubaccounts(list, row));
I have implemented a few iterations of my processing method but the current is below.
protected virtual void CreateSubaccounts(List<NPGeneratedSub> subs, NPMasterSubGeneratorFilter filter)
{
if (filter.NewSegment ?? false)
{
try
{
SegmentMaint segGraph = PXGraph.CreateInstance<SegmentMaint>();
segGraph.Segment.Update(segGraph.Segment.Search<Segment.dimensionID, Segment.segmentID>(AADimension.Subaccount, filter.SegmentID.Value));
SegmentValue value = segGraph.Values.Insert(new SegmentValue() { Value = filter.SegmentValue, Descr = filter.Description });
segGraph.Actions.PressSave();
}
catch
{
throw new PXOperationCompletedSingleErrorException(NonProfitPlusMessages.SegmentValueCannotCreate);
}
}
SubAccountMaint subGraph = PXGraph.CreateInstance<SubAccountMaint>();
NPSubAccountMaintExtension subGraphExt = subGraph.GetExtension<NPSubAccountMaintExtension>();
subGraphExt.save.ConfirmSaving = false;
Sub newSub;
bool errored = false;
foreach (NPGeneratedSub sub in subs)
{
PXProcessing<NPGeneratedSub>.SetCurrentItem(sub);
try
{
newSub = subGraph.SubRecords.Insert(new Sub() { SubCD = sub.SubCD, Description = sub.Description });
subGraph.Save.Press();
subGraph.Clear();
PXProcessing<NPGeneratedSub>.SetProcessed();
}
catch (Exception e)
{
PXProcessing<NPGeneratedSub>.SetError(e);
errored = true;
}
}
if (errored)
{
throw new PXOperationCompletedWithErrorException();
}
}
What needs to be adjusted to allow the buttons action to be triggered on press after processing results have been returned?
After stepping through the javascript I discovered that it wasn't sending a request to the server when you click the cancel button on this screen after processing. The reason is because SuppressActions is getting set to true on the Cancel PXToolBarButton. I compared what I was seeing on this screen to what was happening on screens that work correctly and realized that Acumatica is supposed to set SuppressActions to true on the Schedule drop down PXToolBarButton but for some reason, on this screen, it is incorrectly setting it to true on whatever button is after the Schedule drop down button.
I looked through the code in PX.Web.UI and it looks like they set SuppressActions to true when a drop down button is disabled and PXProcessing adds a FieldSelecting event to the Schedule button which disables the button after you click process. However, I didn't notice any obvious issues as to why the code would be setting it on the wrong PXToolBarButton so someone will likely need to debug the code and see what's going on (we are unable to debug code in PX.Web.UI.dll).
I tried commenting out the other grids in the aspx file that aren't related to the PXProcessing view and this resolved the issue. So my guess would be that having multiple grids on the PXProcessing screen somehow causes a bug where it sets SuppressActions on the wrong PXToolBarButton. However, since the multiple grids are a business requirement, removing them is not a solution. Instead, I would suggest moving all buttons that are after the schedule button to be before the schedule button. To do this, just declare the PXActions before the PXFilteredProcessing view in the graph.
Please try this
Override IsDirty property
Use PXAction instead of PXCancel
Add PXUIField attribute with enable rights
action name should start from lowercase letter
delegate name should start from uppercase letter
see code below
public override bool IsDirty => false;
public override bool IsProcessing
{
get { return false;}
set { }
}
public PXAction<NPMasterSubGeneratorFilter> cancel;
[PXUIField(MapEnableRights = PXCacheRights.Select)]
[PXCancelButton]
protected virtual IEnumerable Cancel(PXAdapter adapter)
{
NPMasterSubGeneratorFilter row = Filter.Current;
if (row != null)
{
this.Clear();
Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentID>(Filter.Current, row.SegmentID);
if (!(row.NewSegment ?? false)) Filter.SetValueExt<NPMasterSubGeneratorFilter.segmentValue>(Filter.Current, row.SegmentValue);
}
return adapter.Get();
}

How to run a void function in the background

I have created a windows form application (CLR Project). When i click the start button, a void function with while loop runs. But the problem is the windows form becomes non-responsive (Not Responding). What I want to do is I want to run the function in the background when the button is clicked and make it stop with a click too and be able to use the windows form. Please help.
My code looks like this:
bool isStarting = false;
btnStart_Click(System::Object^ sender, System::EventArgs^ e){
if(isStarting){
isStarting = false;
}else{
isStarting = true;
runCode(param1, param2, param3);
}
}
void runCode(param1, param2, param3){
while(isStarting){
//do something
}
}
Not Responding occurs when program fall into infinite loop.
In your program, you used infinite loop in runCode method.
This will be fixed when you use thread for infinite loop or else.
So I suggest you to use thread.

OnEnterBreakMode does not fire

Running VS 2012. I created an add-in. I want to handle the OnEnterBreakMode event. The Exec method is called. I tried returning handled = true/false. The handler is never invoked. I tried a few variations of DTE and DTE2. I go to the Tools menu > click "MyAddIn1" and the Exec method is called. I verified the event is bound. I do not know how the life cycle of an add-in works.
StartEvents is not an override and it's not connected to anything. I find that strange...
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if(commandName == "MyAddin1.Connect.MyAddin1")
{
//handled = true;
// Place the following code in the Exec method of the add-in:
EnvDTE.DebuggerEvents debugEvents = _applicationObject.Events.DebuggerEvents;
debugEvents.OnEnterBreakMode += new _dispDebuggerEvents_OnEnterBreakModeEventHandler(Connect.BreakHandler);
return;
}
}
}
private DTE _applicationObject;
private AddIn _addInInstance;
// Place the following Event handler code in the add-in:
// Needed to activate event handlers in Connect.Exec.
public static void StartEvents(DTE dte)
{
Console.WriteLine("Events are attached.");
}
// OnEnterBreakMode Event.
public static void BreakHandler(dbgEventReason reason, ref dbgExecutionAction execAction)
{
Console.WriteLine("Debugger enters break mode. " + "Reason: " + reason.ToString());
}
If you create a local variable for an event, and subscribe to it, then the variable will be freed by GC after scope is left, and your event handler won't fire.
If you make debugEvents a member variable then it should work fine.

Visual C++ - MFC how to change disablity of a button using edit box

I'm currently working with MFC, and I want to make a simple account managment.
I made a login button which is set to disabled from the start and 2 edit box which each one of them is a user id and a password.
I want to make a simple thing : if one of the edit box has no value at all then make the loggin button disabled, else..make the button available.
However, the code doesn't work at all.
this is the code :
part of the header file
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
// Value of the "Username" textbox
CString m_CStr_UserID;
// Control variable of the "Username" textbox
CEdit m_CEdit_ID;
// Value of the "Password" textbox
CString m_CStr_UserPass;
// Control variable of the "Password" textbox
CEdit m_CEdit_PASS;
// Control variable of the "Login" button
CButton m_Btn_Login;
public:
afx_msg void OnEnChangeEditId();
afx_msg void OnEnChangeEditPass();
proceed to the .cpp
.....
void CTestDlg::OnEnChangeEditId()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
// TODO: Add your control notification handler code here
m_CEdit_ID.GetWindowTextW(m_CStr_UserID);
if(!m_CStr_UserID.IsEmpty() && !m_CStr_UserPass.IsEmpty())
m_Btn_Login.EnableWindow(TRUE);
m_Btn_Login.EnableWindow(FALSE);
}
void CTestDlg::OnEnChangeEditPass()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
// TODO: Add your control notification handler code here
m_CEdit_PASS.GetWindowTextW(m_CStr_UserPass);
if(!m_CStr_UserPass.IsEmpty() && !m_CStr_UserID.IsEmpty())
m_Btn_Login.EnableWindow(TRUE);
m_Btn_Login.EnableWindow(FALSE);
}
What's wrong with the code?
In both handlers it's always being enabled FALSE. I think you're missing an else
You either need to return after the EnableWindow(TRUE) or use an else.
acraig5057 explained the problem with your code and showed you how to work around it. I will just add that you can also do this: map the EN_CHANGE for both edit controls to one handler, let's call it OnEnChangeEditIdOrPass:
void CTestDlg::OnEnChangeEditIdOrPass()
{
m_Btn_Login.EnableWindow((m_CEdit_ID.GetWindowTextLength() != 0) &&
(m_CEdit_PASS.GetWindowTextLength() != 0));
}
The function GetWindowTextLength returns the number of characters in the specified edit control and 0 if the edit control is empty.
The logic of the above code is this: if both the edit boxes have characters in them, the && will return TRUE and the login button will be enabled. If at least one of them does not, the && will return FALSE and the login button will be disabled.
Of course, this code not save the values of the username and the password edit controls into string variables, like your code does, but you can always call GetWindowText from inside the handler of the login button.

Blocking action of CDialog's maximize/minimize button

I am using mfc CDialog. I need to show the close and minimize/maximize button, but they should not close or maximize the dialog. I have overriden OnClose method and kept the dialog open even if close button is clicked. But I am unable to block maximize and minimize of the dialog as there doesn't seem to be a OnMaximize method. Is there an alternative way?
You need to handle the WM_SYSCOMMAND message, watching for wParam == SC_MAXIMIZE.
If you catch the SC_MINIMIZE, you can do what you want and not pass it on to Windows.
msdn
Found this snippet here.
const int WM_SYSCOMMAND= 0x0112;
const int SC_MAXIMIZE= 0xF030;
protected override void WndProc(ref Message m)
{
if(m.Msg==WM_SYSCOMMAND)
{
if((int)m.WParam==SC_MAXIMIZE)
{
MessageBox.Show("Maximized!!");
return; // swallow the message
}
}
base.WndProc (ref m);
}
You can not show at all the minimise/maximise icons i your dialog. You can do that by going to Dialog properties (right vlick on your Dialog Contorol --> Properties), Select Styles pain and unselect 'Minimise Box', 'Maximise Box'.

Resources