I have a custom user control which consists of nothing more than an image of an artificial horizon. I want to move it up and down. When there was part of the main form I was able to constantly update the location using the form.OnPaint method. Now that I've moved the image to a separate class the UserControl.OnPaint method is only called when I do things like move the frame.
What's the best way to update the location of the image with each new value? Right now I'm doing something like this...
public void setPitchAngle(double pitchAngle)
{
PitchAngle = pitchAngle;
this.Invalidate();
//this.Update();
}
This works but I'm not sure whether or not this is the right way. The call to update doesn't work because its called from a thread other than the creating thread but I know how to fix that if it also needs to be called.
Related
I've noticed that when OnElementPropertyChanged is fired on a VisualElement like a BoxView, the properties of the underlying platform view are not updated at that time.
I want to know when the VisualElement's corresponding platform view is finished rendering, something like:
this.someBoxView.ViewHasRendered += (sender, e) => {
// Here I would know underlying UIView (in iOS) has finished rendering
};
Looking through some code inside of Xamarin.Forms, namely VisualElementRenderer.cs, it would seem that I could raise an event after OnPropertyChanged has finished. Something like:
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
SetBackgroundColor(Element.BackgroundColor);
else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
UpdateClipToBounds();
else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName)
SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));
// Raise event
VisualElement visualElement = sender as VisualElement;
visualElement.ViewHasRendered();
}
Naturally there's a few more complexities to adding an event to the VisualElement class, as it would need to be subclassed. But I think you can see what I'm after.
While poking around I've noticed properties on VisualElement like IsInNativeLayout. But that only seems to be implementing in Win/WP8. Also, UpdateNativeWidget on VisualElementRenderer as well, however I can't figure out the proper way to leverage them.
Any ideas?
Much appreciated.
TL;DR : Run away, do not go down this path...
On iOS everything that displays content to the screen happens within a UIView (or subclass) and drawRect: is the method that does the drawing. So when drawRect: is done, the UIView is drawing is done.
Note: Animations could be occurring and you might see hundreds of completed rendering cycles completed. You might need to hook into every animation's completion handler to determine when things really are done "rendering".
Note: The drawing is done off-screen and depending upon the iDevice, the screen refresh Hz could 30FPS, 60FPS or in the case of iPad Pro it is variable (30-60hz)...
Example:
public class CustomRenderer : ButtonRenderer
{
public override void Draw(CGRect rect)
{
base.Draw(rect);
Console.WriteLine("A UIView is finished w/ drawRect: via msg/selector)");
}
}
On Android the common way to draw content is via a View or subclass, you could obtain a surface, draw/bilt via OpenGL to a screen, etc... and that might not be within a View, but for your use-case, think Views..
Views have Draw methods you can override, and you can also hook into ViewTreeObserver and monitor OnPreDraw and OnDraw, etc, etc, etc... Sometimes you have to monitor the View's parent (ViewGroup) to determine when drawing is going to be done or when is completed.
Also all standard Widgets are completely inflated via xml resources and that is optimized so you will never see a Draw/OnDraw method call (Note: You should always(?) get a OnPreDraw listener call if you force it).
Different Views / Widgets behave differently and there no way to review all the challenges you will have determining when a View is really done "rendering"...
Example:
public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer,
ViewTreeObserver.IOnDrawListener, ViewTreeObserver.IOnPreDrawListener
{
public bool OnPreDraw() // IOnPreDrawListener
{
System.Console.WriteLine("A View is *about* to be Drawn");
return true;
}
public void OnDraw() // IOnDrawListener
{
System.Console.WriteLine("A View is really *about* to be Drawn");
}
public override void Draw(Android.Graphics.Canvas canvas)
{
base.Draw(canvas);
System.Console.WriteLine("A View was Drawn");
}
protected override void Dispose(bool disposing)
{
Control?.ViewTreeObserver.RemoveOnDrawListener(this);
Control?.ViewTreeObserver.RemoveOnPreDrawListener(this);
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control?.SetWillNotDraw(false); // force the OnPreDraw to be called :-(
Control?.ViewTreeObserver.AddOnDrawListener(this); // API16+
Control?.ViewTreeObserver.AddOnPreDrawListener(this); // API16+
System.Console.WriteLine($"{Control?.ViewTreeObserver.IsAlive}");
}
}
}
Misc:
Note: Layout Optimizations, Content caching, GPU caching, is hardware acceleration enabled in the Widget/View or not, etc... can prevent the Draw methods from being called...
Note: Animation, effects, etc... can cause these these methods to be call many, many, many times before an area of the screen is completely finished displaying and ready for user interaction.
Personal Note: I've gone down this path once due to a crazy client requirements, and after banging my head on the desk for some time, a review of the actual goal of that area of the UI was done and I re-wrote the requirement and never tried this again ;-)
I'm going to answer my own question it hopes that the solution will help someone who is struggling with this issue in the future.
Follow #SushiHangover's advice and RUN don't WALK away from doing something like this. (Although his recommendation will work and is sound). Attempting to listen/be notified when the platform has finished rendering a view, is a terrible idea. As #SushiHangover mentions there's simply too many things that can go wrong.
So what brought me down this path?
I have a requirement for a pin code UI similar to the one in iOS to unlock your device, and in many other apps. When a user presses a digit on the pad I want to update the corresponding display "box" (boxes above the pad). When the user inputs the last digit I want the last "box" to be filled in, in my case a Background color change, and then execution to continue in which the view would transition to the next screen in the workflow.
A problem arose as I tried to set the BackgroundColor property on the fourth box and then transition the screen. However, since execution doesn't wait for the property to change the screen transitions before the change is rendered. Naturally this makes for a bad user experience.
In an attempt to fix it, I thought "Oh! I simply need to be notified when the view has been rendered". Not smart, as I've mentioned.
After looking at some objective C implementations of similar UIs I realizes that the fix it quite simple. The UI should wait for a brief moment and allow the BackgroundColor property to render.
Solution
private async Task HandleButtonTouched(object parameter)
{
if (this.EnteredEmployeeCode.Count > 4)
return;
string digit = parameter as string;
this.EnteredEmployeeCode.Add(digit);
this.UpdateEmployeeCodeDigits();
if (this.EnteredEmployeeCode.Count == 4) {
// let the view render on the platform
await Task.Delay (1);
this.SignIn ();
}
}
A small millisecond delay is enough to let the view finished rendering without having to go down a giant rabbit hole and attempt to listen for it.
Thanks again to #SushiHangover for his detailed response. You are awesome my friend! :D
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.
Is there any way I can pause all UI Update commands in Winforms?
Or I have a slight feeling I'm trying to go about this the completely wrong way, so is there another way around my problem: I basically load a saved state of a control, which loads new controls all over it. However I do some of this in the UI thread, and some of the data loading from another thread, which then fills the UI.
So the effect I have when it is loading is that the user can see a few of the controls appearing in one place, then moving to another place on the form, changing values, etc.
I'd like to get a loading screen instead of this and load the controls in the background. It's quite a large application and its not THAT important so redesigning my code isn't really an option.
Can I simply stop all Update() commands on a control while a method is executing?
You can use the SuspendLayout and ResumeLayout methods to wrap the setup of UI in one operation (without the update of the rendering).
Basically (assuming SomeMethod is in the form class):
private void SomeMethod()
{
this.SuspendLayout();
// all UI setup
this.ResumeLayout();
}
it really depends on your form logic, in general you should not overload the Load or Show method with too much things so that the form can be shown and drawn quickly and always look responsive.
in some cases it could help to use the SuspendLayout and ResumeLayout methods, see here:
Control.SuspendLayout Method
I have a background worker that runs and looks for stuff, and when it finds stuff, I want to update my main WinForm. The issue that I'm having is that when I try to update my WinForm from my background worker, I get errors that tell me I can't modify things that were made outside of my background worker (in other words, everything in my form).
Can someone provide a simple code example of how I can get my code to work the way I want it to? Thanks!
I believe you're looking for the OnProgressChanged Event. More info with example here: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.onprogresschanged.aspx
If I understand correctly, you want to make a change on the form itself, however you cannot change a control on a form from a thread other than the thread the form was created on. To get around this I use the Form.Invoke() method like so:
public void DoSomething(string myArg)
{
if(InvokeRequired)
{
Invoke(new Action<string>(DoSomething), myArg);
}
else
{
// Do something here
}
}
The InvokeRequired property checks the calling thread to determine if it is the proper thread to make changes to the form, if no the Invoke method moves the call onto the form's window thread.
My question is simple: how bad is the following snippet of code? How would you do it?
CancelEventHandler _windowClosing;
private CancelEventHandler WindowClosing
{
set
{
clearEventHandlerList();
this.Closing += value;
_windowClosing = value;
/*
* if calling the method with null parameters,
* it will set up itself as the primary control on the Window
*/
_windowClosing(null,null);
}
get
{
return _windowClosing;
}
}
private readonly CancelEventHandler[] CONTROLS = null;
private int current = 0;
public InitializerForm()
{
InitializeComponent();
/*
* these are the handlers for the different controls,
* in the order of appereance to the user
*/
STATES = new CancelEventHandler[] { handler1, handler2, etc. };
WindowClosing = CONTROLS[0];
}
private void clearEventHandlerList()
{
foreach (CancelEventHandler c in CONTROLS)
{
this.Closing -= c;
}
}
private void handler1(object obj, CancelEventArgs e)
{
if (obj == null)
{
//hide every other control, but this one, also set up if necessary
}
else
{
//do something
WindowClosing = CONTROLS[++current]; // set the next control to show
e.Cancel = true;
}
}
The point would be that the code wouldn't close a form, but instead show another component on it, and the set the way to handle that (this is mobile platform, so clicking OK button on the top generates a closing event). This is because showing several forms (4 or 5) one after another to the user makes the app blink, and also very annoying, while replacing just components is much smoother. This model works, but seems very nasty, and I would like a cleaner way to handle this.
Update:
I updated the code sample so that variable names are somewhat speaky. Still, I'm convinced this is awful, (a) but not sure how much, and more importantly, (b) how to do it better.
Update 2:
So, it seems that the code is still a bit mysterious.
Now here's what the problem is:
I show the user a form, which instructs him what to do in several languages. He proceeds by clicking OK on the window. Next, I ask for his language, and then a few questions (where his/her GPS is, etc.) like this. After he could answer the questions (this shouldn't take more than a few seconds each), I show him a splash screen (I load stuff in a separate thread meanwhile), which has a picture. Showing these forms one after another makes the whole application start slow, and filled with UI lags.
Here's what I do to work around the lags: I put the content of the windows into panels, and put those panels one on another, and hide every one of them but the one that should be visible to the user. (current variable) Each of the windows does different things, so I need to change handler of the window closing event in addition. In this code the part which enables the panel is in the same function (handler1, handler2, etc.) with the part which handles the window closing event. If the arguments are null, it does the former, if it isn't (that means it was triggered by the user) it does the latter.
I need an extensible solution to this so that I can insert and remove dialogs anytime I want (the order and the pointers to the functions are stored in the CONTROLS field, and this seems to be very convenient, if you actually understand it. Although it is never easy to change the entire content of a form, there ought to be a simpler way to do this, as well a nicer one, that is what I'm looking for.
I hope this time I could explain how the model works.
I think it might be theoretically possible to make that code more delightfully diverting, perilously puckish, jovially jeopardous, cheerily chancy and unwarily whimsical but it would require some serious thought.
somehow your code makes me want to cry, i´m sorry. i read it twice and all i know about it is that it "doesStuff" with "STATES".
if you really want some help on this one you will have to work on it yourself first...
Use, XML! It's human-readable!
More seriously-
It seems like you're trying to create some sort of configuration wizard, so I'd start by researching that. Regarding your particular solution, I generally advocate very strongly against the "layered panel" approach. I do so because I maintain apps written by people who found this approach, or the related "hidden tabs on a tab control" approach, to be a good idea. It's not, and maintainers will curse your name for years to come.
That being said, what alternatives are there? Well, one alternative is what you've already dismissed because of its "flicker". I'd say that, in general, the flicker isn't that big of a deal for a quick and dirty application. It might be a good idea to make sure that your new window is called up before closing the old one, for example. (I'm assuming this is possible, I haven't developed on a mobile device.)
Another possibility might be a less-evil version of your layered panels. Instead of throwing a half-dozen panels into one form, create a separate user control for each wizard page and then add/remove the user controls to a containing form. This can avoid your flicker and will prove to be much easier to maintain because each page is in a different control. This might also ease any subsequent "Back" button functionality and make your data structures more naturally defined because those user controls will be associated with a specific logical bit of data. It's still not ideal, but it's probably good enough for a one-off solution.
A third technique, if you foresee extensive wizard modification as the product matures, might be to generalize the creation of your user controls by defining them in a more logical/declarative manner (e.g. via XML). If you dynamically generate sensible controls based on XML, then modifying the panels might be as easy as diving into your XML and doing something like:
<Questions>
<Question type="Text"> <!-- generate a textbox for the answer field -->
Favorite Color:
</Question>
<Question type="Number" range="0-255"> <!-- Maybe this is a spinner -->
The answer to life, the universe, and everything:
</Question>
</Questions>
That's just off the top of my head, and completely overkill for any one-off application, but it's a possibility.
Now, let me caveat this by saying this might work, but it may not be the answer to your real problem - that of a slow and unresponsive UI when you have a lot of forms. The real answer may be to just go ahead and do all separate forms, but have each form load its child forms in a background thread while the user is staring at the first form.
But assuming you're still set on this, I'd start off by making a separate class just to handle the Panel stacking/hierarchy. Call it PanelManager. You would instantiate the PanelManager and associate it with the main form, then add Panels to it (perhaps keyed to a String) and set the order. In the main form, have the closing handler call PanelManager.CloseCurrentPanel() and if there are no more Panels to show then it's time to close the main form.
Time for pseudo-code! Here's a quick idea for the class, i'll leave it to you to implement it:
public class PanelManager {
// constructor
public PanelManager (Form ownerForm);
// short-cut properties
public Panel this[int idx]
{ get; set; }
public int Index
{ get; set; }
// main functionality
public int AddPanel (Panel p);
public void SetPanelOrder (Panel p, int idx);
public void RemovePanel (Panel p);
public void RemovePanelAt (int idx);
// shows the first Panel
public void Show ();
// shows Panel[idx]
public void Show (int idx);
// adds the panel to the top of the stack and displays it
// returns the index of the panel
public int AddPanelAndShow (Panel p);
// hides the current panel, displays the one underneath it
// returns false if there are no more panels
public bool HideCurrentPanel ();
}
in the constructor for the main form, instantiate it by new PanelManager (this), then in the closing event handler, call panelManager.HideCurrentPanel () and then figure out whether or not you need to close it after that.