How do I perform sequential BringIntoView calls in WPF? - c#

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.

Related

Is there a way to see UI updates while debugging?

At the moment I'm trying to debug some code in which I'm cheking for the visibility of items (with the .IsVisible() method for example). The problem is, when I'm jumping from one breakpoint to the next or jumping between lines, the data obviously changes, but the UI of the program doesn't seem to change at all. That makes it a bit difficult for me to tell if things are visible and I have to trust Visual Studio.
Is there a way I can make the UI update while debugging, so I can see the changes over there as well?
You have to force a synchronous re-render of the UI. You could define this extension method somewhere:
public static void SynchronouslyRedraw(this UIElement uiElement) {
uiElement.InvalidateVisual();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { })).Wait();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { })).Wait();
}
and call it on your Window after each breakpoint (using the immediate window, a breakpoint action, an additional line of code etc). It should synchronously re-render the Window in question:
this.SynchronouslyRedraw(); // assuming your breakpoint is in your `Window` class for example.
Note that this method works on any UIElement that is in the visual tree of a Window object.
The UI only updates when you give it a chance - that means not occupying the UI thread with other work (such as your event handler).
There isn't a simple way to do this in the debugger in WPF. In Windows Forms, you can just use Application.DoEvents();, but WPF requires you to await Dispatcher.Yield();, so you can't just execute it whenever you want. Both solve the same problem in much the same way - they give the message loop an opportunity to handle all the pending messages, and then go back to where you left off. Both also have the same weakness - they introduce an opportunity for reentrancy, so be careful.

How to know when a navigation transition ends using UWP's Frame?

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.

Winform app, force execute OnLoad Event when focus is on another tab

I have a WinForm app, the form has TabControl, control has three tabs tabPage1,tabPage2,tabPage3.
The Tab 'tabPage3' is hosting a User defined control which internally has one or more child controls.
Now my problem lies in tabPage3,
I know it is a pure Winforms behavior, until your parent is not activated child controls Onload event won't fire.
I have a requirement to force the Onload event to fire when the focus is on tabPage1, tabPage2. Is there any way to force the Onload event to fire.
I have already visited following links but didn't find any clue. Link Link Link
This is a very unusual requirement, strongly smells like an XY problem. The Load event is heavily over-used in Winforms, a side-effect of it being the default event for a Form or UserControl. One of the behaviors inherited from VB6, the Load event was a big deal in that language. What you want can easily be accomplished by not giving Winforms a choice:
public UserControl3() {
InitializeComponent();
CreateHandle();
}
The CreateHandle() call does the forcing, OnLoad will immediately run. But do be aware that this happens very early, too early to do the kind of things that you'd really want to use OnLoad() or the Load event for. Which are rather limited, it is only truly necessary to discover the actual Location and Size of the control. Anything else belongs in the constructor. Surely including the code that you now run in OnLoad().
Strongly favor using the constructor instead.
I had a similar problem for a previous project, for my needs I managed to just iterate over every tab page in the forms constructor (or possibly OnLoad I can't remember) and then reset the index back to 0 before ever showing the end user.
Something similar to:
for(int i = 1; i < tabControl.TabCount; i++)
tabControl.SelectTab(i);
tabControl.SelectTab(0);

Maps control SetView works only on the page's first load

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.

Silverlight Application UI updating

Hi I have a problem when I am working on my Windows Phone silverlight C# application.
So I want to do something like this
Press a button on page one. The button click handler calls a async method MakeRequest in other class to retrieve data. The async method will fire a event DataReadyEvent and has the result wrapped as DataEventArgs.The handler of this event will be in Page2. So after add a handler to this event, I navigate to Page2 from current page.
I want to retrieve data by a event handler in Page 2 code behind and update that on UI. But the event handler is static (so that I can add it by using Page2.handler_method_name in page1 code without creating a new instance of the page.). Since the handler method is static, I cannot use Dispatcher.Invoke and get back to the UI thread and update UI.
So in this case, anyone has any idea to it? I just want to call a async method in page1, and update result to UI in page2. Thank you
Here is an idea: don't make it static. Don't try to create problems for yourself by breaking simple OOP rules like encapsulation, etc and by finding some crazy workarounds around the framework you work with.
When you are in such a situation you should stop, look back and think because it is an indication that you do something completely wrong. Don't try to push it even further by finding hacks and workarounds. Rather you should refactor and reuse the correct paradigm.
For example, if you want to display the result on Page2, then there IS a Page2 ALREADY. So there IS an instance of it. Why do you want to use static handler then?
Probably because you don't have a reference to this page. That's fine, normally you shouldn't.
But when you finish your computation you can publish an event saying "hey, here is the task done". At that point you shouldn't care who is interested in this result, that's not the worker's concern.
Which means that the logic of the computation itself should probably be moved out from Page1. Really, pages concern is dome presentation logic, nothing more.
Page1 should make a request that some computation needs to be done. And here will be an external component (perhaps something in your ViewModel) to actually make it happen.
So when the result is ready to be consumed, you can simply push it into a ViewModel (update some observable properties or collections, etc), so if there is any UI (or many of them, or other components) interested in this data it will be automatically notified and the data will be displayed.
But please don't try to hack around, it will lead you to bigger pain in the future.

Categories

Resources