I use ReactiveUI and Prism in my Xamarin.Forms application.
Prism, on one side controls the page appear/disappear signal and sends the disappear event just before the view-navigation animation starts.
ReactiveUI on the other side offers a view.WhenActivated(...) extension to only establish view-bindings when the view is shown. The view.WhenActivated() gets disposed when the disappear event is fired.
Since Prism is sending the disappear event while the view-to-unload is still shown, the view bindings gets dissolved in front of the customers eyes.
To prevent this I tried to register my own copy of ReactiveUI's implementation of IActivationForViewFetcher (ReactiveUI.XamForms.ActivationForViewFetcher) and delay the disappear event just as long as the animation isn't over yet (1 second).
var appearing = Observable.FromEvent<EventHandler, bool>(
eventHandler =>
{
void handler(object sender, EventArgs e) => eventHandler(true);
return handler;
},
x => page.Appearing += x,
x => page.Appearing -= x)
// timestamp the event for later take-last scan
.Timestamp();
var disappearing = Observable.FromEvent<EventHandler, bool>(
eventHandler =>
{
void handler(object sender, EventArgs e) => eventHandler(false);
return handler;
},
x => page.Disappearing += x,
x => page.Disappearing -= x)
// timestamp the event for later take-last scan
.Timestamp()
// slow-down the disappearing event to avoid visual un-binding effects
// like re-enabled buttons, values unset and so on.
.Throttle(TimeSpan.FromSeconds(1))
.ObserveOn(RxApp.MainThreadScheduler);
return Observable
.Merge(appearing, disappearing)
.Scan(new Timestamped<bool>(false, DateTimeOffset.MinValue),
(last, n) => n.Timestamp >= last.Timestamp ? n : last)
.Select(v => v.Value)
.DistinctUntilChanged();
This works well in case the user is slow but for some scenarios the disappear-delay may render the active page as deactivated.
Now to my question:
Is there a easier way to delay the disappear signal but avoid it as a whole just in case the page was re-activated in the delay-window?
A bit more information: the appear and disappear events may be triggered by the frameworks more than once so that a de/activated page may be de/activated again without effect.
Thanks
Related
I've been searching for a couple of days now and am running into an issue no matter what I've tried. The problem is that I seem to have come across with the perfect storm and I can't get all 3 things working at the same time.
Pagination
Optional Parameters
Parameter Dialog Prompt
So this first method is what I've been using and everything works except it won't Navigate past past 2 (And I've very aware of why navigation doesn't work)
// ##################################################################################################################################################
// METHOD 1: Everything works correctly except you can't go past page 2
protected void Page_Load(object sender, EventArgs e)
{
CrystalReportViewer1.ReportSource = Session["myReportDoc"] as CrystalDecisions.CrystalReports.Engine.ReportDocument;
if (CrystalReportViewer1.ReportSource == null)
{
//Generate the Report Document
Handlers.ReportHandler myReportHandler = new Handlers.ReportHandler();
CrystalDecisions.CrystalReports.Engine.ReportDocument myReportDocument = myReportHandler.GenerateReport("AlarmStatusReport");
Session["myReportDoc"] = myReportDocument; //This is were we save it off for next time
CrystalReportViewer1.ReportSource = myReportDocument;
}
}
So knowing that the common fix is to not use Page Load but use Page_Init instead. This fixes the Navigation... until I open a report that has optional parameters. With those, every time I try to navigate to the next page, instead of it working, the Parameter box re-appears and now requires at least 1 of my Optional Parameters to be filled out. (Each "next Page" reduces the prompt by 1 Optional). But, because I'm being forced to change the Parameters, it "refreshes" the report and I'm back on Page 1.
// ##################################################################################################################################################
// METHOD 2: Works, but not for any report that has Optional Parameters. They become "Required" and keep popping up instead of navigating to the next page
protected void Page_Init(object sender, EventArgs e)
{
CrystalReportViewer1.ReportSource = Session["myReportDoc"] as CrystalDecisions.CrystalReports.Engine.ReportDocument;
if (CrystalReportViewer1.ReportSource == null)
{
//Generate the Report Document
Handlers.ReportHandler myReportHandler = new Handlers.ReportHandler();
CrystalDecisions.CrystalReports.Engine.ReportDocument myReportDocument = myReportHandler.GenerateReport("AlarmStatusReport");
Session["myReportDoc"] = myReportDocument; //This is were we save it off for next time
CrystalReportViewer1.ReportSource = myReportDocument;
}
}
Now, I got real excited, because I got a bit clever and fixed both those issues, by trapping the Navigation and keeping track of the Page myself. EVERYTHING WORKS NOW!!! until I go to the Parameter Dialog prompt and it was totally jacked up.
// ##################################################################################################################################################
// METHOD 3: Everything works correctly except the Prompt Box doesn't Format correcly due to the addition of the added Event Handers
protected void Page_Load(object sender, EventArgs e)
{
CrystalReportViewer1.ReportSource = Session["myReportDoc"] as CrystalDecisions.CrystalReports.Engine.ReportDocument;
if (CrystalReportViewer1.ReportSource == null)
{
//Generate the Report Document
Handlers.ReportHandler myReportHandler = new Handlers.ReportHandler();
CrystalDecisions.CrystalReports.Engine.ReportDocument myReportDocument = myReportHandler.GenerateReport("AlarmStatusReport");
Session["myReportDoc"] = myReportDocument; //This is were we save it off for next time
CrystalReportViewer1.ReportSource = myReportDocument;
//Init our Manual Page Counter to 1
HiddenFieldPageNumber.Value = "1";
}
CrystalReportViewer1.Navigate += CrystalReportViewer1_Navigate; //Simply Adding this event, EVEN IF IT HAS NO CODE, Breaks the style and formating of the Parameter Prompt box.
CrystalReportViewer1.PreRender += CrystalReportViewer1_PreRender;
}
private void CrystalReportViewer1_Navigate(object source, CrystalDecisions.Web.NavigateEventArgs e)
{
//This prevents this event from Incrementing the Page again when the PreRender Event
//below re-sets which page to show.
if (_SkipPageIncrement == true)
{
return;
}
//Whenever the Navigation is used, this Event fires. Here is the problem, there is nothing that actually tells
//us if the user clicked on Previous or Next (or GotoPage for that Matter). So we have to do some guessing here
if (e.CurrentPageNumber == 1 && e.NewPageNumber == 2)
{
//If they pressed "NEXT" we will always get Current = 1 and New = 2 due to the Pagination starting over on the PostBack
//So we INCREMENT our real Page Number Value.
HiddenFieldPageNumber.Value = (Convert.ToInt32(HiddenFieldPageNumber.Value) + 1).ToString();
}
else if (e.CurrentPageNumber == 1 && e.NewPageNumber == 1)
{
//If they pressed "PREV" we will always get Current = 1 and New = 1 due to the Pagination starting over on the PostBack
//So we DECREMENT our real Page Number Value.
HiddenFieldPageNumber.Value = (Convert.ToInt32(HiddenFieldPageNumber.Value) - 1).ToString();
}
}
private void CrystalReportViewer1_PreRender(object sender, EventArgs e)
{
//The Viewer has a method that allows us to set the page number. This PreRender Event is the only
//Event I could find that works. It comes AFTER the Navigate, but before the reports is rendered.
_SkipPageIncrement = true; //The ShowNthPage re-triggers the Navigation, so this prevents it from running again.
CrystalReportViewer1.ShowNthPage(Convert.ToInt32(HiddenFieldPageNumber.Value));
}
As commented above, the moment I add the OnNavigation Event, even if I comment out all the actual code inside, my Prompt box goes from looking like this...
To this (my page as a dark background and you can see that now shows, plus the "OK" button is all jacked up.
I just don't get why trapping the Navigation Event breaks the Prompt box even when the event is not firing (on that first load).
Side note: I'm using VS 2019 with CR 13.0.3500.0
So thanks to the help of a teammate that is more adept on CSS as I am, I have resolved the issue "good enough". So for anyone who wants to use the LOAD event, (Or has to like me), but then loses the ability to use the navigation and wants to use my method, the band-aid for the Crystal Reports Parameter prompt is to simply override their Styling in you Site.css with this...
/*---------------------- Custom CSS for Report Prompt Buttons ----------------------*/
.pePromptButton {
padding-bottom:4.3px;
}
td.pePromptButton {
display: inherit;
}
img {
vertical-align:top;
}
My Dialog is a simple Frame with an Image, a label to display a question and two more labels (Yes / No) with TapCommand.
I've set up the container with the DialogPage.xaml and DialogPageViewModel and injected in the ViewModel I want to open the dialog.
Here is the code I'm using to call the Dialog:
public void ShowDialog()
{
_dialogService.ShowDialog("DiscardPopup", CloseDialogCallback);
}
void CloseDialogCallback(IDialogResult dialogResult)
{
var goBack = dialogResult.Parameters.GetValue<bool>("GoBack");
if (goBack)
NavigationService.GoBackAsync();
}
If the user taps over the "Yes label", I execute this command:
YesCommand = new DelegateCommand(() => YesTapped());
private void YesTapped()
{
IDialogParameters pa = new DialogParameters();
pa.Add("GoBack", true);
RequestClose(pa);
}
If the user taps over the "No label", I simply call:
NoCommand = new DelegateCommand(() => RequestClose(null));
The "problem" is when the ShowDialog is fired, the DiscardPopup is taking up to 3 seconds to show up.
Is there a way to make it faster?
The same happens with the TapCommands, 2 - 3 seconds when the RequestClose is invoked.
Without actual code telling you exactly what the issue is, is going to be best guess. Based on your feedback to my comments above I would suggest the following:
Try displaying the dialog on a test page that doesn't have a complex layout. My guess is that you won't see such a long load time. If that's the case this would point to your layout being overly complex and that the lag time is due to the device struggling to re-render the View
Try using Prism.Plugin.Popups. You'll need to initialize Rg.Plugins.Popup and register the DialogService. You can see docs on that at http://popups.prismplugins.com
I have an activity indicator on xaml page. Initially its IsVisible property is false. I have a button on page. When user click on button it calls a web service to get data. I change the value of IsVisible property to true before calling the service so that activity indicator starts to display on page and after successful calling of service I change its value to again false so that it doesn't show any more on page.
But it is not working. I know the actual problem. When we call the web service the UI thread gets block and it doesn't show the activity indicator.
How I can enable the UI thread when web service gets called so that activity indicator can show on page until we get the data?
Try making your webservice call into an async and await it.
Depending on how you've structured things you may have to use a TaskCompletionSource as the following example demonstrates.
In this example when the button is clicked, the button is made invisible, and the ActivityIndicator is set to IsRunning=True to show it.
It then executes your long running task / webservice in the function ExecuteSomeLongTask using a TaskCompletionSource.
The reason for this is that in our button click code, we have the final lines:-
objActivityIndicator1.IsRunning = false;
objButton1.IsVisible = true;
That stop the ActivityIndicator from running and showing, and also set the button back to a visible state.
If we did not use a TaskCompletionSource these lines would execute immediately after calling the ExecuteSomeLongTask if it was a normal async method / function, and would result in the ActivityIndicator not running and the button still being visible.
Example:-
Grid objGrid = new Grid()
{
};
ActivityIndicator objActivityIndicator1 = new ActivityIndicator();
objGrid.Children.Add(objActivityIndicator1);
Button objButton1 = new Button();
objButton1.Text = "Execute webservice call.";
objButton1.Clicked += (async (o2, e2) =>
{
objButton1.IsVisible = false;
objActivityIndicator1.IsRunning = true;
//
bool blnResult = await ExecuteSomeLongTask();
//
objActivityIndicator1.IsRunning = false;
objButton1.IsVisible = true;
});
objGrid.Children.Add(objButton1);
return objGrid;
Supporting function:-
private Task<bool> ExecuteSomeLongTask()
{
TaskCompletionSource<bool> objTaskCompletionSource1 = new TaskCompletionSource<bool>();
//
Xamarin.Forms.Device.StartTimer(new TimeSpan(0, 0, 5), new Func<bool>(() =>
{
objTaskCompletionSource1.SetResult(true);
//
return false;
}));
//
return objTaskCompletionSource1.Task;
}
You need to do your work in an asynchronous way. Or in other words: Use Asnyc & Await to ensure, that you UI works well during the call.
You can find more informations in the Xamarin Docs.
async and await are new C# language features that work in conjunction
with the Task Parallel Library to make it easy to write threaded code
to perform long-running tasks without blocking the main thread of your
application.
If you need further asistance, please update your question and post your code or what you have tried so far.
I'm looking at rehosting the workflow designer. I want to be able to run some code whenever the user adds an activity to the designer canvass. Is there an event that fires when the user adds an activity at design time? Or is there an event on the activity that I can consume? Thanks!
For anyone who might stumble onto this, here's what I figured out...
First off, when creating the workflow designer, you need to subscribe to the ModelChanged event.
_workflowDesigner = new WorkflowDesigner();
_workflowDesigner.Load(new Sequence());
ModelService ms = _workflowDesigner.Context.Services.GetService<ModelService>();
if (ms != null)
ms.ModelChanged += new EventHandler<ModelChangedEventArgs>(ms_ModelChanged);
My event handler looks like this...
void ms_ModelChanged(object sender, ModelChangedEventArgs e)
{
if (e.ItemsAdded != null && e.ItemsAdded.Count<ModelItem>() == 1)
{
ModelItem item = e.ItemsAdded.FirstOrDefault<ModelItem>();
var test = item.GetCurrentValue() as MyActivityType;
if (test != null && test.Id == null)
{
//do whatever initialization logic is needed here
}
}
}
I need to give credit to this source for pointing me in the right direction.
One thing to be careful of - when you move an activity within the model, two events are raised, a remove and an add. At this point, I don't need to worry about whether I'm adding or moving an activity as I can tell whether it has been initialized, but if you need to know whether something has really been added to the model, you might need to track both events.
I am developing an online exam application using asp.net. In the start exam page I have created a javascript countdown timer.
How can I move to the next page automatically after the timer reaches 00?
Here is my code:
long timerStartValue = 1000 ;
private int TimerInterval
{
get
{
int o =(int) ViewState["timerInterval"];
if(o==0)
{
return (o);
}
return 50 ;
}
set
{
ViewState["timerInterval"] = value;
}
}
protected void Page_PreInit(object sender,EventArgs e)
{
string timerVal = Request.Form["timerData"];
if(! String.IsNullOrEmpty(timerVal))
{
timerVal = timerVal.Replace(",", String.Empty) ;
this.timerStartValue = long.Parse(timerVal);
}
}
protected void Page_Load(object sender, EventArgs e)
{
if(! IsPostBack)
{
this.timerStartValue = 10000; //3599000;//14400000;
this.TimerInterval = 500;
}
}
protected void Button1_Click(object sender, EventArgs e)
{
this.timerStartValue = 3599000;
}
protected void Page_PreRender(object sender, EventArgs e)
{
System.Text.StringBuilder bldr=new System.Text.StringBuilder();
bldr.AppendFormat("var Timer = new myTimer({0},{1},'{2}','timerData');", this.timerStartValue, this.TimerInterval, this.lblTimerCount.ClientID);
bldr.Append("Timer.go()");
ClientScript.RegisterStartupScript(this.GetType(), "TimerScript", bldr.ToString(), true);
ClientScript.RegisterHiddenField("timerData", timerStartValue.ToString());
}
Thanks in advance,
sangita
It sounds like when you click the "Next" button, you are loading an entirely new page. This of course changes all the content and resets all the javascript. You can't maintain state across pages without a bit of work.
The solution to this could be to save the timer state when the next button is pressed, and pass it to the next stage. You could do this by saving the timer state to a hidden form input and submitting it along with the Next button.
The other option would be to load your questions via AJAX. Instead of moving to a new page every time the next button is clicked, you could simply replace the question portion of the page with a new question, and leave the timer intact. This is probably the solution I would use.
Are u reloading the entire page when clicking on the next button ? That may leads to realod the java script file also.So the variable values will reset.May be you can think about showing the questions /answers via Ajax.You need not reload the entire page when showing the next question.the part when you show the quiz will only be updated.so you can maintain the global variables in your java script too. Check the below link to know about partial page updating using jQuery.
http://www.west-wind.com/presentations/jquery/jquerypart2.aspx
Hope this helps.
You can put the timer in an iframe if you can't get rid of the postback.
You need a way to persist information between pages, and there's really only one possibility: To make it part of the next page request.
Now, this could be subdivided into 2 categories:
1) As part of the url: http://www.example.com/page?timer=123;
2) As part of the headers;
And number 2 opens new possibilities:
a) As part of POST data;
b) As a client-side cookie only;
c) As a cookie tied to information on the server;
Number 1, 2a and 2b can be manipulated by the user. So what you can do is store some value in a cookie, a hash for example or a database row ID, that you'll use to fetch information on the server.
tl;dr? Use a asp "Session object". It lets you keep things on the server-side and users will have no idea what they are.