I'm working on an app in Xamarin.Forms, and things have been going pretty steadily, until I ran into a navigation error. The thing that mystifies me about it is that I've already been successfully using the same code calls on other pages, but suddenly with this page, it isn't working.
I have designed a bit of a unique navigation flow because of the visual result that I'm trying to accomplish, using a combination of master-detail that has two tiers of navigation pages using the normal push / pop code. I was following suggestions from this article on medium.com.
The app initializes a main page called "Root Navigation" that initializes the master and detail pages.
public App()
{
InitializeComponent();
MainPage = new RootNavigation();
}
Root Navigation page:
public partial class RootNavigation : MasterDetailPage
{
private MenuPage menuPage;
private NavigationPage OuterPage;
private NavigationPage InnerPage;
public RootNavigation()
{
this.Master = this.menuPage = new MenuPage();
this.menuPage.MenuItemsListView.ItemSelected += Menu_ItemSelected;
var viewModel = new SelectEmployeeViewModel();
var page = new SelectEmployeePage(viewModel);
SetAsDetailPage(page);
}
To navigate forward in the app, I'm using a method called "set as detail page," that bridges the gap between master-detail behavior and navigation push / pop behavior.
private void SetAsDetailPage(ContentPage page)
{
NavigationPage.SetHasNavigationBar(newPage, false);
if (newPage.GetType() == typeof(JobDetailPage))
{
newPage.ToolbarItems.Add(
new ToolbarItem()
{
Text = "Back",
Command = new Command(() => BackButton_Clicked())
});
}
this.InnerPage = this.InnerPage ?? new NavigationPage();
this.InnerPage.Navigation.PushAsync(page);
this.OuterPage = this.OuterPage ?? new NavigationPage(this.InnerPage);
this.Detail = this.Detail ?? this.OuterPage;
}
Then, navigating backward calls one of two methods: "BackButton_Clicked()" or "ReturnToJobList()".
private void ReturnToJobList()
{
while (InnerPage.CurrentPage.GetType() != typeof(JobsPage))
{
var current = InnerPage.CurrentPage.ToString();
System.Diagnostics.Debug.WriteLine($"'{current}' attempting Navigation.Pop()");
InnerPage.PopAsync();
}
}
private void BackButton_Clicked()
{
this.InnerPage.PopAsync();
}
All of the pages that display read-only data have navigated without issue. When I'm done with the page, I have it raise a call to the MessagingCenter, and the root navigation receives the message and performs the desired navigation. For example, the "MenuPage_ItemSelected" event handler fires the code:
if (e.SelectedItem.ToString().ToLower() == "log out")
{
Device.InvokeOnMainThreadAsync(() =>
{
InnerPage.PopToRootAsync();
this.IsPresented = false;
});
}
This seems to be working fine, after I spent a while researching that when a secondary page calls 'pop to root' on a background thread, I have to invoke it on the main thread.
Ok, so finally to the problem page: The "update job notes" page. The page raises a call to the Messaging Center, and the Root Navigation page picks that up and executes the following code:
private async void SaveJobNotes(ContentPage sender)
{
if (sender is UpdateJobNotesPage notesPage)
{
bool result = await SaveNewJobNote(notesPage);
var message = result ? "Saved changes" : "An error occurred; changes not saved";
await DisplayAlert("Save", message, "OK");
}
ReturnToJobList();
}
Stepping through the code, it correctly executes SaveNewJobNote() and returns true. Then, it awaits displaying the alert "Saved Changes". Then, the code then gets stuck in an infinite while loop in the "ReturnToJobList()," forever printing out into the debug output [0:] 'Application.Views.UpdateJobNotesPage' attempting Navigation.Pop(). After about a million cycles I get tired of waiting and quit the debugger. I can't seem to do anything to make the page go away!
I've tried a bunch of stuff with investigating the differences between PopAsync and PopModalAsync. After checking what's on the navigation stacks for the different pages in question, everything looks exactly like what I'd expect -- there's nothing on the modal stacks for anything (because I never called PushModalAsync on anything), there's 0 on the RootNavigation stack, 1 on the OuterPage stack, and 4 on the InnerPage stack. That all makes perfect sense to me, but it still doesn't pop the Update Job Notes page. I also tried code with Navigation.RemovePage(page) with no success. The only difference there was that the debugger included printing a warning about my code and suggesting I use PopAsync() instead.
I also tried some different things with making the PopAsync() call from this.Navigation, this.Outer, this.Outer.Navigation, this.Inner, this.Inner.Navigation, all with no success.
I have already looked at a bunch of other questions on Stack Overflow including this question and this question but none of them seem to apply in this case. Any assistance would be greatly appreciated!!
I remember this was happening to me.
I forget exactly what was the cause, but I had some funky navigation going on as well. My problem was around popups and at one point, they were creating a new stack. So when I popped, I was getting unexpected results.
I would suspect you are also creating another stack somewhere, especially if you are at 0 in the debugger.
The culprit is most likely lurking around that InvokeOnMainThread().
I haven't really figured out what the problem is with the code block that I created that was supposed to call InnerNavigation.PopAsync() until the Job List page was visible. It seems like all the variables in that code block evaluate to values that I'd expect, but somehow it doesn't seem to be able to pop anything off the stack.
However, I did change my code block that handles saving Job Notes, and it does now pop the Save Job Notes page off the stack.
private async void SaveJobNotes(ContentPage sender)
{
this.InnerPage.PopAsync(); //I don't understand why this works and the
//other didn't, but it correctly pops the page
if (sender is UpdateJobNotesPage notesPage)
{
bool noteSaved = await SaveNewJobNote(notesPage);
bool progressSaved = await SaveJobProgress(notesPage);
var message = noteSaved && progressSaved ?
"Changes were save successfully" :
"An error occurred; changes not saved";
await DisplayAlert("Save", message, "OK");
}
}
Related
So I am trying to execute a method when the page has loaded. The OnAfterRender() override method is too early in my case. the method I am trying to do is in my #code{} block of the razor page.
I basically want to execute getAvailablePrinters when the page is loaded.
as requested my code below:
#code {
private List<string> Printers;
private List<string> LayoutTypes;
private void sendPrint()
private async Task getAvailablePrinters()
{
//get layouts
Layouts = new List<Layout>();
AvailablePrintersRepository availablePrintersRepository = new AvailablePrintersRepository();
try
{
Layouts = await availablePrintersRepository.getAvailablePrintersAsync();
}
catch (Exception e)
{
//show message
}
//sort printers & layouts
Printers = new List<string>();
LayoutTypes = new List<string>();
foreach (Layout layout in Layouts)
{
foreach (string printer in layout.Printers)
{
if (!Printers.Contains(printer))
{
Printers.Add(printer);
}
}
if (!LayoutTypes.Contains(layout.Type))
{
LayoutTypes.Add(layout.Type);
}
}
}
}
#AccessDenied I want to send a request to another API to get back data i need to display to the user. i now have a button to do it but i want to get the data after the page has loaded so the user doesn't have to press the button each time
--
Because the method isn't finished when the page has loaded so that is why I want to do it after the page has loaded
So you believe that the OnAfterRenderAsync and OnAfterRender are called too early in the pipeline, and thus are not fit for the Web Api call you want to do in order to retrieve data, right ?
You are wrong, they are, in my opinion too late for this enterprise, and you should use the OnInitializedAsync life cycle method to execute the HTTP request.
Please see the VS template how a Web Api is made to populate the ForeCast objects
in the FetchData page.
You should try code in various situations to understand how the initialization process works, and see that your ideas or perceptions are wrong. Understand this: You should retrieve your data before your page is rendered, not after it is being rendered. OnAfterRender(Async) may be used to execute code that otherwise it's too early to execute. It is most often used to initialize JS objects.
Hope this helps...
I am creating a Form when a certain event occurs. I put this created Form into a static member of the class where it is created. I debugged the code and everything works fine but the Form stays blocked and the user can't do anything in this window. It just appears with a loading animation (see picture). So nothing in the opened window is clickable, you can't even close it.
class CallManagementObserver : CallObserver
{
private static FrmIncomingCall frmCurrentCall;
public CallManagementObserver()
{
}
public void callChangedEvent(CallEv[] events)
{
foreach (CallEv currentEvent in events)
{
switch (currentEvent.getID())
{
case TermConnRingingEv.ID:
// Incoming call
frmCurrentCall = new FrmIncomingCall(currentEvent);
frmCurrentCall.Show();
frmCurrentCall.Update();
break;
case CiscoCallInfoChangedEv.ID:
// User accepted external call on terminal
frmCurrentCall.Close();
break;
case TermConnActiveEv.ID:
// User is in call
frmCurrentCall.Close();
break;
case ConnDisconnectedEv.ID:
// Caller has hung up
frmCurrentCall.Close();
break;
default:
break;
}
}
}
}
}
As you can see above I wrote my own Form class whose code is here:
public partial class FrmIncomingCall : Form
{
Call incomingCall;
CallEv currentEvent;
public FrmIncomingCall(CallEv currentEvent)
{
InitializeComponent();
this.currentEvent = currentEvent;
this.incomingCall = currentEvent.getCall();
}
private void initGui()
{
Connection[] callConnections = incomingCall.getConnections();
Address caller = callConnections[1].getAddress();
lblIncomingCallSource.Text = caller.getName();
}
private void btnAcceptCall_Click(object sender, System.EventArgs e)
{
TermConnEv termConnEv = (TermConnEv)currentEvent;
TerminalConnection termConn = termConnEv.getTerminalConnection();
termConn.answer();
}
private void frmIncomingCall_Load(object sender, System.EventArgs e)
{
initGui();
}
}
When I show the Form via ShowDialog() it is usable but the program stops (since this is what dialogs are made for I guess).
Any ideas what I'm doing wrong? Nothing freezes, the program is running correctly.
Well, your application is poorly designed... It seems that you have no idea of what multithreading is and why you should use it.
If the application hangs forever, then either there is a deadlock (something like the dialog wait on the calling system and the calling system wait on the dialog).
As I have no idea what CallEv is and how it is intended to be used.
Well, if the calling system works and the UI is never updated, then obviously, you never let the UI have time to be updated because your UI thread is 100% of the time using the calling system or waiting on it.
That means that the calling system should probably be used from another thread and that you should have some communication between both threads...
It might also be possible that the calling system might be used in many different ways (as it would be the case for serial port and TCP communication) where one could use what fit most with his application.
Another problem with your code is that when you close a dialog, as far as I know it cannot be used anymore without recreating the dialog as the dialog would be disposed... So you would need to set the formCurrentCall to null and update any affected code. Alternatively, you might hide the form instead and show it again when required.
In any case, it is hard to help you because we don't have any idea of what is CallEv and other classes or events in your code. Also, we have no idea which code is executing when the UI is not responding (or updated). So the question do not have enough informations. In fact, such problem are way easier to debug using a debugger as it is far easier to see what code is run and which line of code take time to execute or even to see which code is not executed.
I am showing a content dialog on one of my sessionstateaware pages, I close the dialog fine and can reopen another on the same page as many times as I want. When I navigate away and come back and try to reopen my dialog, it blows up with this error:
WinRT information: Only a single ContentDialog can be open at any time.
Additional information: An async operation was not properly started.
I have tried numerous things, but it seems whenever I navigate away and come back, somehow the reference to the control is lost and another gets created?
Here is a little snippet of the problem area:
if(asyncCommand != null)
asyncCommand.Cancel();
var result = new ContentDialogResult();
if (CanOpenNewDialog)
{
CanOpenNewDialog = false;
MyContentDialog.Title = "Homebuyer Options - " + apt.Customer1FullName;
asyncCommand = MyContentDialog.ShowAsync();
result = await asyncCommand.AsTask();
}
Facing the same issue with ContentDialouge when navigating more than one time. App is just crashing by saying "An async operation was not properly started. Only a single ContentDialog can be open at any time."
I got a solution for this. For example the method Show Dialouge, where the ContentDialog is calling, should be awaited. So make the Show Dialouge method as a task.
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
await ShowDialouge();
}
private async Task ShowDialouge()
{
ContentDialog dialouge = new ContentDialog();
await dialouge.ShowAsync();
}
The problem was when I was navigating using prism, every time I came back to my page I resubscribed to my event that was creating a dialog. To fix this I simply call unsubscribe OnNavigatedFrom.
Issue
I am running tests using the Coded UI Test builder and writing all the code from scratch. The issue I am facing is in the middle of the test there is a popup message with the results "Stay on this page" or "Leave this page". I want my test to be able to click "Stay on this page".
The popup sometimes appears straight after the event or sometimes appears a couple of seconds later.
Code
So the event that I run before the message appears is a button click:
ClickButton(browser, "login");
void ClickButton(UITestControl parent, string id)
{
var button = new HtmlButton(parent);
button.SearchProperties.Add(HtmlButton.PropertyNames.Id, id);
Mouse.Click(button);
}
I have tried Keyboard.SendKeys() but this just sends the keys to the browser window. I have also tried using the recording tool. Both are unsuccessful.
After this event I need to wait for the popup to appear and click "Stay on this page". Does anyone know how to achieve this?
We actually mapped this confirmation window and it works for us.
We start with a Window with name = Windows Internet Explorer
Followed by a Pane with name = Windows Internet Explorer
and finally with a Button of name Stay on this page
All with Technology = MSAA.
You must handle writing code to wait for readiness of the control and proper time out if you don't expect the confirmation every time.
Hope this helps.
Depending on what exactly that window is, you should be able to deal with it no problem. I would use the inspector to get the properties of the window and use one of the WaitFor* methods to give it some time. Here is an example of dealing with the security pop up that IE shows:
namespace CaptainPav.Testing.UI.CodedUI.PageModeling.Win
{
public class IESecurityWindow<T> : PageModelBase<WinWindow>
{
protected const string SecurityWindowName = "Internet Explorer Security";
internal protected override WinWindow Me => new WinWindow().Extend(WinWindow.PropertyNames.Name, SecurityWindowName, PropertyExpressionOperator.EqualTo);
protected WinButton AllowButton => this.Me.Find<WinButton>(WinButton.PropertyNames.Name, "Allow", PropertyExpressionOperator.EqualTo);
internal readonly T AllowModel;
public IESecurityWindow(T allowModel)
{
this.AllowModel = allowModel;
}
public T ClickAllow()
{
// if not IE, this will return false and the next model is returned; change the time if you need more or less wait
if (this.AllowButton.IsActionable(3000))
{
Mouse.Click(this.AllowButton);
}
return AllowModel;
}
}
}
In this case, the dialog is a Win* type, not Html*. There are some custom extension methods sprinkled in to make the searching and stuff easier, but this should give you an idea. If interested, the extensions (which are written by me) can be found on github.
I am attempting maintenance on a system I did not write (and aren't we all?). It is written in C Sharp and JavaScript, with Telerik reports.
It has the following code included in JavaScript that runs when the user clicks a button to display a report in a separate window:
var oIframe = $("iframe id='idReportFrame' style='display:none' name='idReportFrame' src=''>");
oIframe.load(function() { parent.ViewReports(); });
oIframe.appendTo('body');
try
{
$('#idReportForm').attr('target', 'idReportFrame');
$('#idReportForm').submit();
}
catch (err) { // I did NOT write this
}
Then the load function:
function ViewReports()
{
var rptName = $("#ReportNameField").val();
if (rptName == '') { return false; }
var winOption = "fullscreen=no,height=" + $(window).height() + "left=0,directories=yes,titlebar=yes,toolbar=yes,location=yes,status=no,menubar=yes,scrollbars=no,resizable=no, top=0, width=" + $(window).width();
var win = window.open('#Url.Action("ReportView", "MyController")?pReportName=' + rptNameCode, 'Report', winOption);
win.focus();
return false;
}
When I execute this (in Chrome, at least), it does pop up the window and put the report in it. However, breakpoints in the c# code indicate that it is getting called 2 or 3 times. Breakpoints in the JavaScript and examination of the little log in the JavaScript debugging environment in Chrome show that the call to win.focus() fails once or twice before succeeding. It returns an undefined value, and then it appears that the first routine above is executed again.
I am inclined to think it some kind of timing issue, except that the window.open() call is supposed to be synchronous as far as I can tell, and I don't know why it would succeed sometimes and not others. There is a routine that gets executed on load of the window, perhaps that's somehow screwing up the return of the value from open().
I am not a JavaScript person much, as those of you that are can likely tell by this time. If there is something with the code I've put here that you can tell me is incorrect, that's great; what I'm more hopeful for is someone who can explain how the popup-report-in-frame is supposed to work. Hopefully I can do it without having to replace too much of the code I've got, as it is brittle and was not, shall we say, written with refactoring in mind.
From what I could find the window.open will return null when it fails to open. Something may be keeping the browser from opening additional windows a couple of times; maybe it is a popup blocker.
The actual loading of the url and creation of the window are done asynchronously.
https://developer.mozilla.org/en-US/docs/Web/API/Window.open
Popup blocking
In the past, evil sites abused popups a lot. A bad page could open
tons of popup windows with ads. So now most browsers try to block
popups and protect the user.
Most browsers block popups if they are called outside of
user-triggered event handlers like onclick.
For example:
// popup blocked
window.open('https://javascript.info');
// popup allowed
button.onclick = () => {
window.open('https://javascript.info');
};
Source: https://javascript.info/popup-windows
I just ran into this and it seems to be because I had a breakpoint on the line that calls window.open and was stepping through the code, in Chrome dev tools. This was extremely hit-and-miss and seemed to fail (return null, not open a window, whether one already existed or not) more times that it succeeded.
I read #Joshua's comment that the creation is done asynchronously, so I figured that forcing the code to 'stop' each time I step might be screwing things up somehow (though on a single line like var w = window.open(...) doesn't seem like this could happen).
So, I took out my breakpoint.. and everything started working perfectly!
I also took note of https://developer.mozilla.org/en-US/docs/Web/API/Window/open where they specify that if you are re-using a window variable and name (the second argumen to window.open) then a certain pattern of code is recommended. In my case, I am wanting to write HTML content to it, rather than give it a URL and let it async load the content over the network, and I may call the whole function repeatedly without regard for the user closing the window that pops up. So now I have something like this:
var win; // initialises to undefined
function openWindow() {
var head = '<html><head>...blahblah..</head>';
var content = '<h1>Amazing content<h1><p>Isn\'t it, though?</p>';
var footer = '</body></html>';
if (!win || win.closed) {
// window either never opened, or was open and has been closed.
win = window.open('about:blank', 'MyWindowName', 'width=100,height=100');
win.document.write(head + content + footer);
} else {
// window still exists from last time and has not been closed.
win.document.body.innerHTML = content;
}
}
I'm not convinced the write call should be given the full <html> header but this seems to work 100% for me.
[edit] I found that a Code Snippet on Stackoverflow has a some kind of security feature that prevents window.open, but this jsfiddle shows the code above working, with a tweak to show an incrementing counter to prove the content update is working as intended. https://jsfiddle.net/neekfenwick/h8em5kn6/3/
A bilt late but I think it's due to the window not beeing actually closed in js or maybe the memory pointer not being dereferenced.
I was having the same problem and I solved it by enclosing the call in a try finally block.
try {
if (!winRef || winRef.closed) {
winRef = window.open('', '', 'left=0,top=0,width=300,height=400,toolbar=0,scrollbars=0,status=0,dir=ltr');
} else {
winRef.focus();
}
winRef.document.open();
winRef.document.write(`
<html>
<head>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
${$(id).remove('.print-exclude').html()}
</body>
</html>
`);
winRef.document.close();
winRef.focus();
winRef.print();
} catch { }
finally {
if (winRef && !winRef.closed) winRef.close();
}