I'm using Frame.Navigate and I want to know when the resulting navigation transition (animation) ends.
My code looks like this:
myframe.Navigate(typeof(MyPage), parameter: null, new SlideNavigationTransitionInfo()
{
Effect = SlideNavigationTransitionEffect.FromRight
});
Using SlideNavigationTransitionInfo works correctly and I can see the slide animation.
However I can't find the way to be notified once the animation completed.
I tried subscribing to Frame.Navigated, but that event isn't related to the animation. It fires once the view is available from the Frame.Content property, which is too soon in my situation.
I also tried subscribing to Page.Loaded, but that is also too soon because that event is fired when the view entered the visual tree. (I say it's too soon because the view is visible during the animation, which means it had time to load before the animation.)
I don't know what to try next.
Related
We're developing an app using xamarin.forms and using the MVVM pattern. We're using the built in navigation system to push and pop pages. Now we're running into an issue when navigating pages using commands.
Say we have a view with a button that navigates to another page. This navigation is done by binding a viewmodel command to the button. The command has a canExecute check, which checks the "IsBusy" property, to prevent the button from being clicked twice, and initiating two page navigations.
Example code:
view:
<Button Command={Binding NavigateCommand} />
viewmodel (i've left out the notifypropertychanged and ChangeCanExecute calls, which do exist):
Public bool IsBusy {get => _isBusy set => {_isBusy = value;}}
Public ICommand NavigateCommand = New Command(Navigate, () => !IsBusy);
async private Task Navigate()
{
IsBusy = true;
await Navigation.PushAsync(new OtherPage());
IsBusy = false;
}
Now you would say that this works, which is kind of does. However, we've implemented custom transition animations in Android that slides the new page into view from the right, and removes the old one by sliding it out of view to the left. The problem is, that the PushAsync method is awaited, but it completes as soon as the page is loaded, but before the animation to move it into view has completed. So during the transition animation, the command can execute again, and it is still possible to execute the command twice.
TLDR:
Is there any way to wait for page transition animations to finish when using PushAsync?
Right,
I got some suggestions via other channels about using animation listeners. I've been fiddling a bit but I was unable to use them to solve the issue. I've taken a look in the xamarin forms source code, and it seems that internally, the navigationpagerenderer also doesn't use an event, but uses a constant (const int TransitionDuration = 220;) to wait for the transition to finish. However, we use a longer animation, that causes the issue. In the master branch of xamarin.forms, the TransitionDuration variable has become virtual, so we can override once they release a version with this code included.
This still seems like a bit wonky solution to me. It would be better to have some way of having an event when the transition finishes, but it seems the even the people writing xamarin.forms haven't been able to accomplish this.
If anyone has a better suggestion I would really like to hear it
I'm having an issue with the user interface on project I'm working on. It involves "nodes" and connecting them together. I have the connecting of two nodes together tied to pressing a button on an initial node and then clicking the other node to connect to it. The latter half is implemented by checking for when Event.current.type == EventType.MouseDown then joining the initial node with the node you last hovered over.
This works fine most of the time however I noticed that sometimes when you clicked on another node it would not join them together instantaneously, but until you click off of the node. After doing Debug.Log(Event.current.type) it showed that sometimes the event was coming up as "used" when I clicked, instead of "mouseDown" and as such would not perform the join code until I clicked somewhere else. It seems to only happen for some nodes.
Here are two gifs of the behaviour with the console output:
Problem code:
private bool detectEscape()
{
Debug.Log(Event.current.type);
return (Event.current.type == EventType.MouseDown);
}
This function is returning False sometimes on mouse clicks due to the event being "used" sometimes. It is called in the GUI.
Know of any reason what causes the current event to be used? I do comparisons like above in a number of places in my code. Could that be what is causing the current event to be used? How do I avoid it?
Am I using the Event system correctly?
Know of a better way to capture mouse clicks? Using Input.GetMouseButtonDown(0) unfortunately isn't an option as it only works when the game is running and this program is meant to be an extension to the editor.
Otherwise, know of a way to put a break point in Unity 3D's source code so that I can put one in the the function Event.Use() and determine what is consuming the mouse event?
Presumably you are calling detectEscape from OnGUI somewhere, right? The current event is only valid during OnGUI. Additionally, OnGUI can be called multiple times per frame, with different current events. Sometimes the event type might be Repaint, sometimes it might be MouseDown, sometimes it might be something else. So if the event type is not MouseDown, you don't want to assume that the mouse is not down; the mouse might still be down but a different event might be occurring.
I'm converting an IE window's handle to mshtml.HTMLDocumentClass using the way as described here.
There are a couple of really weird things happening here.
When I hook into the onreadystatechange event for frame document (get the frame using document.getElementById and hook into frame onreadystatechange), the event gets fired only once.
Problem #1 disappears when I detach the event handler and reattach it to the same method.
THE MAIN PROBLEM , after doing this the web document freezes, textboxes don't receive focus , buttons can be clicked but they appear like read only buttons All text type controls become readonly, after digging through the problem a little , I found that this behaviour appears as soon as you hook into the frame document's onreadystatechange event. Detaching from the event brings the page back to normal behaviour.
Here's the code which I'm using.
this.GuideDocument = this.GetHtmlDocumentByWindowHandle(handle) as mshtml.HTMLDocumentClass;
var frame = (this.GuideDocument.frames.item(0) as mshtml.IHTMLWindow2).frames.item(1) as mshtml.IHTMLWindow2;
var frameDocument = frame.document as mshtml.HTMLDocumentClass;
frameDocument.HTMLDocumentEvents2_Event_onreadystatechange += frameDocument_HTMLDocumentEvents2_Event_onreadystatechange;
void frameDocument_HTMLDocumentEvents2_Event_onreadystatechange(IHTMLEventObj pEvtObj)
{
frameDocument.HTMLDocumentEvents2_Event_onreadystatechange -= frameDocument_HTMLDocumentEvents2_Event_onreadystatechange;
frameDocument.HTMLDocumentEvents2_Event_onreadystatechange += frameDocument_HTMLDocumentEvents2_Event_onreadystatechange;
this.DisableButton("btnClose", string.Empty, framedocument);
}
I'm not exactly sure as to what's missing here, Do you see anything wrong here, or any alternate approach. This web page is composed of many framesets and thus frames. any insights into this will be a gr8 help.
I somehow feel, that it requires some base.onreadystatechange thing, but I don't know if there is one available. Or some other way to let the original event get called etc..
Thanks,
We have a TreeView in our application with the following requirements:
When an item is added:
The newly-added item is scrolled into view
The parent of the newly added item is also scrolled into view.
If they are too far away to both be seen at the same time, the item takes precedence.
This seems easy, simply scroll the parent into view first, then scroll the child.
The problem is when you call it like this:
parent.BringIntoView();
child.BringIntoView();
...only the second one seems to have any effect. The first one is basically ignored.
I then tried wrapping the second call in a BeginInvoke() call like this:
parent.BringIntoView();
Dispatcher.BeginInvoke((Action)(() => {
child.BringIntoView();
}));
Which does work, but now you can visibly see the TreeView scroll twice; once for the parent, then a moment later, for the child, which just looks bad.
So how can I call BringIntoView back-to-back but without the double-refresh issue of using the dispatcher?
Try using the Loaded event instead of the dispatcher. According to this article, it's a perfect fit for situations like this:
... we initially implemented the Loaded event so that
it would fire just after the window was rendered, but before any input
was processed. We figured that if it was ready enough for input, it
was ready enough for load-time initialization. But then we started to
trigger animations off of the Loaded event, and saw the problem; for a
split second you’d see the content render without the animation, then
you’d see the animation start. You might not always notice it, but it
was especially noticeable when you run the app remotely.
So we moved
Loaded so that it now fires after layout and data binding have had a
chance to run, but just before the first render. (And note that if
you do anything in your Loaded event handler that invalidates layout,
it might be necessary to re-run it before rendering.)
In other words, on Loaded you have the most up to date information about the physical layout of the element, but it hasn't actually rendered yet, so you should be safe from any "screen flicker" issues.
EDIT: To answer your question in the comments, you can wire up events "local" to the current method using a closure, like this:
EventHandler handler = null;
handler = (sender, e) => {
this.LayoutUpdated -= handler; // only run once
child.BringIntoView();
};
this.LayoutUpdated += handler;
By defining the handler inside the method, you are able to access the method's local variables (child) from within. Very similar to the Dispatcher call.
I'm not sure if relying on LayoutUpdated is a good idea, actually. It happens quite often so it may end up firing sooner than you need. It happens twice for individual Width and Height settings, for example. Another one to look into is ScrollViewer.ScrollChanged. Or you could avoid BringIntoView altogether and try manually examining the element sizes to calculate where to scroll to.
I have a problem with Bing Maps control I use in my WP7 application.
When I'm navigated to the page with map control, it starts the GeoCoordinateWatcher. When GeoCoordinateWatcher has some location data for me, it calls a SetView() method to center the map at current location. There is also a button on ApplicationBar that's also starting the GeoCoordinateWatcher.
Now, the thing is, when I first navigate to this page, all works fine: GeoCoordinateWatcher starts, gives me the location data after some time, calls SetView() and the map centers when I need it too. The same with the button.
But if I press the Back button to get back to the main page and then try to navigate again to the map page, SetView() stops working. In debugging I see that the code surrounding it works as expected and the data passed to SetView() is correct, but nothing happens with the control and events for changing view don't fire either.
I'm assuming there may be something wrong with map initialization (or disposing on navigating from), but I don't know where to dig.
Edit:
I've tried changing Map.Center directly and it doesn't work in exactly the same way the SetView() doesn't: it works fine the first time page is navigated to and doesn't work on the other times.
Edit 2:
Ok, it gets weirder. In debugging I see that my map's center actually gets set to a correct value. But an actual control shows absolutely different location and me, moving map around, doesn't change the value that I see from code.
Edit 3:
I've added a button to ApplicationBar that just calls SetView() and it works fine. Apparently, the problem appears when I call SetView() as a result of GeoCoordinateWatcher.PositionChanged event raising. How could I work around that?
Try using map.Center = loc.CoOrdinates;
I've found the solution.
In my original code I've subscribed to the GeoCoordinateWatcher.Position property change event in the page constructor: App.PropertyChanged += AtmInfoPageOnAppPropertyChanged
All I had to do was to move that to the OnNavigatedTo event handler and add App.PropertyChanged -= AtmInfoPageOnAppPropertyChanged to the OnNavigatedFrom event handler.
I think, the problem was that keeping the old page subscribed to the event didn't allow it to be disposed, and at the same time didn't allow the new page to subscribe to that same event, thus causing the code to be called for the different page and different map control than the one displayed on the screen.