Blazor - How to manager #ref for initial invisible part of a component - components

I have a Blazor component who is composed of one div included in an #if directive (IsVisible). The div has an #ref. When the component is rendered, IsVisible is false. My component has a method (Show()) who set IsVisible to true and use the reference to the div. But the reference is always null (no Context and no Id).
#if (IsVisible)
{
<div #ref="MyRef">Bla bla bla</div>
}
#code {
bool IsVisible = false;
ElementReference MyRef;
public void Show()
{
IsVisible = true;
StateHasChanged();
// here, MyRef has no Context and no Id
}
}

An example of the classic component misconception: StateHasChanged re-renders the component. NO, it queues a render event onto the Renderer's Queue. In your code this doesn't happen until the end of Show - it's a synchronous block of code, so runs to completion before the Renderer gets any thread time. You are checking for MyRef before it exists.
You need to modify Show.
public async Task Show()
{
IsVisible = true;
StateHasChanged();
// Yields and lets the Renderer service it's queue
await Task.Yield();
// or await Task.Delay(1);
// now we exist
var x = MyRef;
}

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();
}

xsp.extlib.convstate returns null

I have an Xpage application that uses the extension library where the xsp.extlib.convstate is 'null' for one of three users until they manually refresh page. All three users access application via RDP using Citrix and internet options are the same for all three. Trying to figure out why this would be happening. The application is only on one 9.0.1 server.
From the looks of the source code, if there hasn't been a conversationState initialised yet, the conversationState would not be initialised until either:
after the Render Response phase (in the phase listener: com.ibm.xsp.extlib.component.layout.impl.ApplicationPhaseListener)
#SuppressWarnings("unchecked") // $NON-NLS-1$
public void afterPhase(PhaseEvent event) {
if(event.getPhaseId()==PhaseId.RENDER_RESPONSE) {
// After the render phase, we save the conversion state
ConversationState.saveInSession(event.getFacesContext());
}
}
in the setParent method of the UIApplicationLayout, and this seems to be guarded by a 'isRestoringState' condition, which means I don't think this would run on the first view of a page as there wouldn't be any state to restore.
#Override
public void setParent(UIComponent parent) {
super.setParent(parent);
if( null == parent ){ // removing parent
return;
}
// TODO should move this initialization to initBeforeContents instead
FacesContextEx context = (FacesContextEx) getFacesContext();
if(null != context && !context.isRestoringState()) {
ConversationState cs = ConversationState.get(context, FacesUtil.getViewRoot(this), true);
// Initialize the conversation state
// Set the current navigation path to the UserBean
ApplicationConfiguration conf = findConfiguration();
if(conf!=null) {
String navPath = conf.getNavigationPath();
if(StringUtil.isEmpty(navPath)) {
// If there isn't a navigation path that is defined, the use the default one
if(StringUtil.isEmpty(cs.getNavigationPath())) {
navPath = conf.getDefaultNavigationPath();
}
}
if(StringUtil.isNotEmpty(navPath)) {
cs.setNavigationPath(navPath);
}
}
}
}
So this might explain why it wouldn't be initialised until the 2nd page view.
You could try forcing an initialisation of the ConversationState before you try to use it, maybe in beforePageLoad, by calling one of the com.ibm.xsp.extlib.component.layout.ConversationState's get() methods.
Note the boolean parameter tells the method to create the ConversationState if it does not exist.
I don't do much ServerSide Javascript but I guess this works? The sentiment is correct.
#{javascript: com.ibm.xsp.extlib.component.layout.ConversationState.get(facesContext, true); }
If you are doing it in java then:
ConversationState.get(FacesContext.getInstance(), true);
Does this sound like an explanation of why you are seeing your behaviour?

UWP page state manage

I want to learn how to manage the state of a page between navigation.
for example a navigate onto page1 and then i navigate to page2, but when i navigate back to page1, the UI elements must already be there with the same data as before and they must not be re-initialized or data must not be binded again by the compiler.
Also what I can do to manage state of whole application such that, I terminate the app and then when i launch it next time, the same state is already there as last time. can i apply it on whole application? or what if I only want to apply it on a few pages? any help would be appreciated thanks.
or example a navigate onto page1 and then i navigate to page2, but when i navigate back to page1, the UI elements must already be there with the same data as before and they must not be re-initialized or data must not be binded again by the compiler.
For this question, you may use UIElement.CacheMode property and Frame.CacheSize property. CacheSize property sets the number of pages in the navigation history that can be cached for the frame, and CacheMode property sets a value that indicates that rendered content should be cached as a composited bitmap when possible.
As we know, an UWP app default using a rootFrame for displaying several pages, we just use Navigation method to change the content in the frame. You can see this in the OnLaunched(LaunchActivatedEventArgs e) method of a blank UWP app. But how to implement cache function? For example, your app has two page and one root frame. You can define CacheSize property in your OnLaunched(LaunchActivatedEventArgs e) method for example:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
...
// Ensure the current window is active
rootFrame.CacheSize = 2;
Window.Current.Activate();
}
Then in your two pages's constructed functions enable CacheMode property for example:
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Enabled;
}
Also what I can do to manage state of whole application such that, I terminate the app and then when i launch it next time, the same state is already there as last time. can i apply it on whole application?
For this question, you will need to save the page state in the OnSuspending(object sender, SuspendingEventArgs e) method using Frame.GetNavigationState method, and you can save this state into the app's local settings. For example:
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
Frame rootFrame = Window.Current.Content as Frame;
string navstate = rootFrame.GetNavigationState();
var localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["nav"] = navstate;
deferral.Complete();
}
And how to retrieve this informaton? You can override your OnLaunched(LaunchActivatedEventArgs e) method, and at first you will need to judge how is your app be closed last time, by user, or by system using ApplicationExecutionState enumeration, for example like this:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
//#if DEBUG
// if (System.Diagnostics.Debugger.IsAttached)
// {
// this.DebugSettings.EnableFrameRateCounter = true;
// }
//#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
//rootFrame.Navigate(typeof(MainPage), e.Arguments);
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated ||
e.PreviousExecutionState == ApplicationExecutionState.ClosedByUser)
{
object value;
var localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values.TryGetValue("nav", out value))
{
rootFrame.SetNavigationState(value as string);
}
else
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
}
else
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
}
// Ensure the current window is active
rootFrame.CacheSize = 2;
Window.Current.Activate();
}
But be aware that when an app is closed, next time you launch this app, the UI elements will be re-initialized, this function can only navigate to the page when the last time you close your app, but the data in that page will be lost. But you can also save the data to the local settings and when you navigate to the page, set the value to those UI elements.

MKAnnotationView RightCallOut button crashes my app when I click on it

I'm calling a service and returning a bunch of latitudes and longitudes which I'm then placing on a map using MapKit.
using MKAnnotationView I'm adding a RightCallOutButton to each annotation.
So I had to create a new MapDelegate. Code below.
If I click on the button I create the app crashes and I get an error from MonoTouch saying the selector is accings omething that has already been GC'd (garbage collected).
So my question would be, where should I set the RightCalloutAccessoryView and where should I create the button, if not in this code below?
public class MapDelegage : MKMapViewDelegate {
protected string _annotationIdentifier = "BasicAnnotation";
public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation) {
MKAnnotationView annotationView = mapView.DequeueReusableAnnotation(this._annotationIdentifier);
if(annotationView == null) {
annotationView = new MKPinAnnotationView(annotation, this._annotationIdentifier);
} else {
annotationView.Annotation = annotation;
}
annotationView.CanShowCallout = true;
(annotationView as MKPinAnnotationView).AnimatesDrop = true;
(annotationView as MKPinAnnotationView).PinColor = MKPinAnnotationColor.Green;
annotationView.Selected = true;
var button = UIButton.FromType(UIButtonType.DetailDisclosure);
button.TouchUpInside += (sender, e) => {
new UIAlertView("Testing", "Testing Message", null, "Close", null).Show ();
} ;
annotationView.RightCalloutAccessoryView = button;
return annotationView;
}
}
annotationView = new MKPinAnnotationView(annotation, this._annotationIdentifier);
...
var button = UIButton.FromType(UIButtonType.DetailDisclosure);
You should avoid declaring local variables to hold references you expect to outlive the method itself. Once there's no reference to annotationView or button the Garbage Collector (GC) is free to collect them (the managed part) even if it's native counterparts still exists. However when a callback to them is called you'll get a crash.
The easiest solution is to keep a list of them and (at the class level, i.e. a List<MKPinAnnotationView> field) clear the list when you destroy the view. The UIButton should not be necessary since there's a reference between the view and it.
NOTE: work is being done to hide this complexity from developers in future versions of MonoTouch. Sadly you cannot ignore such issues at the moment.

Show modal dialog (top most) in new thread

A modal dialog with top most property set to true, doesn't appear as top most when shown within a new thread. Example code:
Thread thread = new Thread(KickOffForm);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
private void KickOffForm(object state)
{
Thread.Sleep(5000); // Mimics logic that takes place before form is shown
var form = new Form2();
form.ShowDialog();
}
The modal dialog appears as top most if the form is instantiated at the beginning of the thread. Example code:
Thread thread = new Thread(KickOffForm);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
private void KickOffForm(object state)
{
var form = new Form2();
Thread.Sleep(5000); // Mimics logic that takes place before form is shown
form.ShowDialog();
}
The above code is executed within a class that is instantiated when exe starts.
Why would the form appear as top most when instantiated at the beginning of the thread and not if instantiated later on?
Forms can only be modal to the thread they are created and owned by.
If you want to display a modal dialog that stops interaction with your main form, you must create the dialog on the main UI thread.
This must be so, because each thread runs it's own message loop. One thread knows nothing about any message loop in another thread.
Maybe you could invoke the dialog window in a correct thread:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click( object sender, EventArgs e )
{
Thread thread = new Thread( KickOffForm );
thread.SetApartmentState( ApartmentState.STA );
thread.Start();
}
private void KickOffForm( object state )
{
var form = new Form2();
Thread.Sleep( 5000 ); // Mimics logic that takes place before form is shown
this.Invoke( (Action)(() => { form.ShowDialog(); }) );
}
}

Resources