Note: I'm using MonoTouch.
I get the following exception when I click on the tbName (textfield, see its delegate below) for the second time. The first time the modal view comes up without a problem, and I dismiss it (code also provided below). But the second time I click on the tbName (Textfield by the way), I get this exception:
"Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Attemping to being a modal transition from to while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed"
The thing is though, it doesn't matter how long I wait before clicking, so I figure there's a fundamental I'm missing
From the NewEnvelopeViewController class:
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
tbName.TouchDown += delegate(object sender, EventArgs e) {
// Set everything up for the editting of the envelope's name
_dataViewController.NumericPad = false;
_dataViewController.FieldType = NewEnvelopeDataViewController.Fields.Name;
_dataViewController.EnvelopeToEdit = _envelope;
this.PresentModalViewController(_dataViewController, true);
};
From the NewEnvelopeDataViewController class:
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
if (NumericPad) {
tbValueArea.KeyboardType = UIKeyboardType.DecimalPad;
}
else {
tbValueArea.KeyboardType = UIKeyboardType.Default;
}
switch (FieldType) {
case Fields.Name:
NavigationItem.Title = "Enter the name";
tbValueArea.Text = EnvelopeToEdit.Name;
tbValueArea.KeyboardType = UIKeyboardType.Default;
break;
case Fields.Budget:
NavigationItem.Title = "Set your budget";
tbValueArea.Text = EnvelopeToEdit.Budget.ToString();
tbValueArea.KeyboardType = UIKeyboardType.DecimalPad;
break;
case Fields.Remainder:
NavigationItem.Title = "What remains of your budget?";
tbValueArea.Text = "0";
tbValueArea.KeyboardType = UIKeyboardType.DecimalPad;
break;
default:
throw new NotImplementedException("ViewDidAppear has a FieldType not accounted for");
}
}
partial void clickedDone (UIBarButtonItem sender)
{
switch (FieldType) {
case Fields.Name:
EnvelopeToEdit.Name = tbValueArea.Text;
break;
case Fields.Budget:
break;
default: // Not implemented yet
throw new NotImplementedException("FieldType not accounted for yet");
break;
}
this.ParentViewController.DismissModalViewControllerAnimated(true);
}
I've seen an article on StackOverflow regarding doing something with DidAppear, but it didn't seem to work in my case and it wasn't clear what exactly the problem is to me (rather than just patching over it).
Move the following code,
tbName.TouchDown += delegate(object sender, EventArgs e) {
// Set everything up for the editting of the envelope's name
_dataViewController.NumericPad = false;
_dataViewController.FieldType = NewEnvelopeDataViewController.Fields.Name;
_dataViewController.EnvelopeToEdit = _envelope;
this.PresentModalViewController(_dataViewController, true);
};
from the ViewDidAppear method to the ViewDidLoad method. The ViewDidAppear gets triggered everytime the view appears, including when a modal view controller gets dismissed. Hence, what your code right now is doing is adding another handler to the object's TouchDown event so when it is triggered, it always executes one more extra time, resulting in presenting the same modal view controller many times. That's why it works the first time, but the second time it crashes.
In general, if you want to subscribe to events in ViewDidAppear, be sure to unsubscribe them in the ViewDidDisappear or, even better, always subscribe events in ViewDidLoad and unsubscribe in ViewDidUnload.
Related
Good day
I have a new field inside the CROpportunity Extenstion called usrGrossProfit.
During CROpportunity's RowSelected it works out the values as needed. The problem I am having is that the users are using the create Quote button on the form and because of this never saves using the save button, The system does it for them. I have found that because of this the usrGrossProfit value is not saved.
Is there a way to force a save/Persist inside the RowSelected function?
protected void CROpportunity_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
try
{
var row = (CROpportunity)e.Row;
if (row == null) return;
CROpportunityExt SOE = PXCache<CROpportunity>.GetExtension<CROpportunityExt>(row);
int total = 0;
decimal TotalSales = 0;
decimal TotalCost = 0;
foreach (CROpportunityProducts item in this.Base.Products.Select())
{
total++;
CROpportunityProductsExt2 itemExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt2>(item);
TotalCost += (decimal)itemExt.UsrCostPrice.Value * item.Qty.Value;
TotalSales += (decimal)itemExt.UsrSellingprice * item.Qty.Value;
}
SOE.UsrGrossProfit = TotalSales - TotalCost;
// I added this just to try and see if it helps
cache.SetValueExt<CROpportunityExt.usrGrossProfit>(row, (decimal)(TotalSales - TotalCost));
// we are not allowed to press the save button in the event Handler
//this.Base.Save.Press();
}
catch (Exception ex)
{
PXTrace.WriteError(ex);
}
}
I have also tried to override the CreateQuote Function but this doesn't work
public delegate IEnumerable CreateQuoteDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable CreateQuote(PXAdapter adapter, CreateQuoteDelegate baseMethod)
{
this.Base.Persist();
return baseMethod(adapter);
}
I have also made a business event to open and save the Opportunity also with no luck.
No, you shouldn't save on row selected even if it was allowed. This is because row selected event gets fired several times and you don't want to be saving each time.
If you want to save on your CreateQuote override, try this:
Base.Save.PressButton(adapter)
Perhaps a better option, might be to force the user so that it's the user himself who saves. For example, you could check the state and throw an error in your override instead of saving.
if (Opportunity.Current != null && Opportunity.Cache.GetStatus(Opportunity.Current) == PXEntryStatus.Inserted)
{
throw new PXException("Please save before proceeding");
}
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();
}
I need to run some address validation on Customer Location addresses using a 3rd party API to determine if the address is residential or commercial. This validation should run whenever an address field is changed. In other words, the validation should be run in the Address_RowUpdated event handler.
Because the function is calling a 3rd party API, I believe that it should be done in a separate thread, using PXLongOperation so that it does not hold up address saving and fails gracefully if the API is unavailable or returns an error.
However, I am not sure if the architecture of running a long operation within an event handler is supported or if a different approach would be better.
Here is my code.
public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = this.Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
PXLongOperation.StartOperation(Base, delegate
{
RunCheckResidential(location, locationCache);
});
this.Base.LocationCurrent.Cache.IsDirty = true;
}
}
protected void RunCheckResidential(Location location, PXCache locationCache)
{
string messages = "";
PX.Objects.CR.Address defAddress = PXSelect<PX.Objects.CR.Address,
Where<PX.Objects.CR.Address.addressID, Equal<Required<Location.defAddressID>>>>.Select(Base, location.DefAddressID);
FValidator validator = new FValidator();
AddressValidationReply reply = validator.Validate(defAddress);
AddressValidationResult result = reply.AddressResults[0];
bool isResidential = location.CResedential ?? false;
if (result.Classification == FClassificationType.RESIDENTIAL)
{
isResidential = true;
} else if (result.Classification == FClassificationType.BUSINESS)
{
isResidential = false;
} else
{
messages += "Residential classification is: " + result.Classification + "\r\n";
}
location.CResedential = isResidential;
locationCache.Update(location);
Base.LocationCurrent.Update(location);
Base.Actions.PressSave();
// Display relevant messages
if (reply.HighestSeverity == NotificationSeverityType.SUCCESS)
String addressCorrection = validator.AddressCompare(result.EffectiveAddress, defAddress);
if (!string.IsNullOrEmpty(addressCorrection))
messages += addressCorrection;
}
PXSetPropertyException message = new PXSetPropertyException(messages, PXErrorLevel.Warning);
PXLongOperation.SetCustomInfo(new LocationMessageDisplay(message));
//throw new PXOperationCompletedException(messages); // Shows message if you hover over the success checkmark, but you have to hover to see it so not ideal
}
public class LocationMessageDisplay : IPXCustomInfo
{
public void Complete(PXLongRunStatus status, PXGraph graph)
{
if (status == PXLongRunStatus.Completed && graph is CustomerLocationMaint)
{
((CustomerLocationMaint)graph).RowSelected.AddHandler<Location>((sender, e) =>
{
Location location = e.Row as Location;
if (location != null)
{
sender.RaiseExceptionHandling<Location.cResedential>(location, location.CResedential, _message);
}
});
}
}
private PXSetPropertyException _message;
public LocationMessageDisplay(PXSetPropertyException message)
{
_message = message;
}
}
}
UPDATE - New Approach
As suggested, this code now calls the LongOperation within the Persist method.
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = Base.Location.Current;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
locationExt.UsrResidentialValidated = false;
Base.LocationCurrent.Cache.IsDirty = true;
}
}
public delegate void PersistDelegate();
[PXOverride]
public virtual void Persist(PersistDelegate baseMethod)
{
baseMethod();
var location = Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
if (locationExt.UsrResidentialValidated == false)
{
PXLongOperation.StartOperation(Base, delegate
{
CheckResidential(location);
});
}
}
public void CheckResidential(Location location)
{
CustomerLocationMaint graph = PXGraph.CreateInstance<CustomerLocationMaint>();
graph.Clear();
graph.Location.Current = location;
LocationExt locationExt = location.GetExtension<LocationExt>();
locationExt.UsrResidentialValidated = true;
try
{
// Residential code using API (this will change the value of the location.CResedential field)
} catch (Exception e)
{
throw new PXOperationCompletedWithErrorException(e.Message);
}
graph.Location.Update(location);
graph.Persist();
}
PXLongOperation is meant to be used in the context of a PXAction callback. This is typically initiated by a menu item or button control, including built-in actions like Save.
It is an anti-pattern to use it anytime a value changes in the web page. It should be used only when a value is persisted (by Save action) or by another PXAction event handler. You should handle long running validation when user clicks on a button or menu item not when he changes the value.
For example, the built in Validate Address feature is run only when the user clicks on the Validate Address button and if validated requests are required it is also run in a Persist event called in the context of the Save action to cancel saving if validation fails.
This is done to ensure user expectation that a simple change in a form/grid value field doesn't incur a long validation wait time that would lead the user to believe the web page is unresponsive. When the user clicks on Save or a specific Action button it is deemed more reasonable to expect a longer wait time.
That being said, it is not recommended but possible to wrap your PXLongOperation call in a dummy Action and asynchronously click on the invisible Action button to get the long operation running in the proper context from any event handler (except Initialize):
using PX.Data;
using System.Collections;
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> TestLongOperation;
[PXUIField(DisplayName = "Test Long Operation", Visible = false, Visibility = PXUIVisibility.Invisible)]
[PXButton]
public virtual IEnumerable testLongOperation(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate ()
{
System.Threading.Thread.Sleep(2000);
Base.Document.Ask("Operation Done", MessageButtons.OK);
});
return adapter.Get();
}
public void SOOrder_OrderDesc_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
if (!PXLongOperation.Exists(Base.UID))
{
// Calling Action Button asynchronously so it can run in the context of a PXAction callback
Base.Actions["TestLongOperation"].PressButton();
}
}
}
}
Warning: This is my first time using threads and my first time trying out an animation. Please bear with me.
I want to rotate an ImageView. I set up a thread for it:
public class ThreadAnimation extends Thread
{
private ImageView iv;
private RotateTransition rt;
public ThreadAnimation(ImageView iv)
{
this.iv = iv;
}
#Override
public void run()
{
while (true)
{
RotateTransition r = new RotateTransition();
r.setToAngle(360);
r.setCycleCount(1);
r.setDuration(Duration.millis(300));
r.setNode(iv);
r.play();
try
{
sleep(100);
} catch (InterruptedException e)
{
return;
}
}
}
}
I call this inside my controller class, upon pressing a Button.
animation.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle (ActionEvent abschicken)
{
ThreadAnimation thread = null; //ANIMATION PIZZA
if (thread == null)
{
thread = new ThreadAnimation(olivenview);
thread.start();
}
}
});
My ImageView olivenview will rotate just like I wanted it to. However it takes quite a long time until it seems to stop (I can see it because the button triggering it still looks triggered for a while) and when I go ahead to press it a second time afterwards, I get a nonstop error stream with a lot of null pointer exceptions. I am very clueless, can anyone help me out? Is this due to my Thread Setup or does the problem lie somewhere else (in code that I didn't post here)?
I believe you do not need threads for this. Notice the .play() method returns immediately and the animation will run in the background.
That being said, try this.
...
//Create your rotation
final RotateTransition r = new RotateTransition();
r.setToAngle(360);
r.setCycleCount(1);
r.setDuration(Duration.millis(300));
r.setNode(iv);
//When the button is pressed play the rotation. Try experimenting with .playFromStart() instead of .play()
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent action) {
r.play();
}
});
...
On an other note I recommend switching to java 8 so that you can use lambda expressions instead of the anonymous class!
I've attached the KeyDown event to a ListView in my Win 10 UWP app. I want to make VirtualKey.Enter have a special effect, but the event is not firing for this particular key. Neither does it for Space, Arrow up or down. This I guess because the listview already has defined a special behaviour for those keys.
I'd like to override some of those keys though, or at least trigger additional actions. Even attaching events to those key with modifiers (e.g. Shift+ArrowDown) would not work because the events still are not firing.
I read that for WPF that there is a PreviewKeyDown-event which one can attach to. I can't find that event for UWP though. Are there any other options?
Stephanie's answer is a good one and it works in the general case. However, as Nilzor observed it will not work in the case of a ListView for the Enter key. For some reason the ListView handles the KeyDown event in case Enter is pressed.
A better way to handle key events when dealing with a ListView, as the question asks, is this.
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
(sender as ListView).AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(ListView_KeyDown), true);
}
private void ListView_KeyDown(object sender, KeyRoutedEventArgs args)
{
if (args.Key == Windows.System.VirtualKey.Enter)
{
}
}
Notice the last argument in the AddHandler function. This specifies whether we want to handle events already handled by a previous element in the visual tree.
Of course don't forget to unsubscribe from the event when appropriate
Here is one way to do it : subscribe to the global Window.Current.CoreWindow.KeyDown event.
Then save the focus state of your listview and react accordingly.
Here is the code :
public sealed partial class MainPage : Page
{
bool hasFocus = false;
public MainPage()
{
this.InitializeComponent();
Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
}
private void CoreWindow_KeyDown(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.KeyEventArgs args)
{
if(hasFocus)
{
Debug.Write("Key down on list");
}
}
private void myList_GotFocus(object sender, RoutedEventArgs e)
{
hasFocus = true;
}
private void myList_LostFocus(object sender, RoutedEventArgs e)
{
hasFocus = false;
}
You will also need to subscribe to the focus events in xaml, for your ListView :
<ListView .... GotFocus="myList_GotFocus" LostFocus="myList_LostFocus"/>
Corcus's solution doesn't work for me. What is working is handling PreviewKeyDown directly from XAML. Works well for SPACE or ENTER key:
XAML:
<ListView PreviewKeyDown="BookmarksListView_PreviewKeyDown">
Code behind:
private void BookmarksListView_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == Windows.System.VirtualKey.Enter)
{
// DO YOUR STUFF...
e.Handled = true;
}
}
You can use AddHandler method.
private void KeyEnterEventHandler(object sender, KeyRoutedEventArgs e)
{
if (e.OriginalKey == Windows.System.VirtualKey.Enter)
{
PlayFromListView();
}
}
private void LoadListView()
{
foreach (var music in playListStorageFile.PlayList)
{
ListViewItem item = new ListViewItem();
item.AddHandler(FrameworkElement.KeyDownEvent, new KeyEventHandler(KeyEnterEventHandler), true);
TextBlock mytext = new TextBlock();
mytext.Text = music.Nro.ToString() + " - " + music.Name;
mytext.Tag = music.Nro;
item.Content = mytext;
lvMusics.Items.Add(item);
}
}
https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.addhandler?view=winrt-18362