Hi
I'm totally new at Xamarin / C# / Visual Studio. I do have extensive web dev experience in JS. I'm an developing a native macos app and decided Xamarin mac and Skiasharp was the way to go.
Everything was more or less ok until I reach a point in my code where I need to redraw the canvas after reading new data from a connected hardware device that I need to represent on screen graphically.
I've invested already quite some hours into this and haven't been able to find anything specific on how to redraw the canvas fro Xamarin mac. I see that for Xamarin forms the way to go is invalidate
/ invalidateSurface
, but I can't find it's equivalent for Xamarin mac.
Can someone please tell me how am I supposed to force the canvas to redraw?
Kind regards
In Chris' example there's an NSTimer he sets up in the ViewController that calls Invalidate() every 33 milliseconds- that may work for you (this is slightly edited from his code):
public void StartAnimationTimer () { Timer = NSTimer.CreateRepeatingScheduledTimer (new TimeSpan (0, 0, 0, 0, 33), t => { Invalidate (); }); }
Then you can just call that at the end of your ViewController's ViewDidLoad() method.
Answers
I am having a similar issue.
I was able to force a redraw in a Mac client with:
CGRect rect = new CGRect(0f, 0f, Bounds.Width, Bounds.Height);
SetNeedsDisplayInRect(rect);
...being called in a subclass of SKCanvasView, in my case. Obviously your rect may vary depending on the situation.
Thanks @wtfork. I'll give it a try later today. Nonetheless, isn't there a more canonical way to do this within the framework?
I believe I saw a separate thread that suggested that for Mac the responsibility for drawing the screen was left with the host view, but I am maybe a week into Xamarin so don't take me for an expert
Consider filing an issue with skiasharp - https://github.com/mono/SkiaSharp/issues
@wtfork : I called your code within a subclass of
SKCanvasView
, but nothing happens. I guess it is because I had to instantiate a sublcass object to make the code run. And that would create a different instance of theSKCanvasView
object.I guess I need to access the
SKCanvasView
member object declared as an outlet the within theViewController
class held in theViewController.designer.cs
file (see below). Because I guess that is the one that holds theSurface
object that gets passed through the SkiasharpOnPainting
eventHowever, I feel like I don't know a word of what I am talking about, so I might have got it all wrong...
Oh- yeah, I should have clarified. That's what I ended up doing since I wasn't sure how else to gain control of the input accessors (I had to do a fair bit of casting to make all the classes line up– again, making this up as I go along). I created a subclass of SKCanvasView (called PSCanvasView for what it's worth) and in that subclass I overrode MouseDown, and in the body of MouseDown called SetNeedsDisplayInRect. I expect you can call it externally too, though you'd need to pass the rect in.
There is almost certainly a more elegant way to do this but I'm new to both Skia and Interface Builder (all of my previous C# experience is in Unity.)
I see @wtfork . The thing is that my app has absolutely no UI controls. It just opens a window and displays data graphically. So hijacking mouse events is not a way to go for my. I need to trigger a redraw each time I read from the attached hardware device...
There has to be a proper way to trigger canvas repaints programmatically without the involvement of the UI controls...
In any case, any idea of how I can access the
SKCanvasView
class externally?@ChrisHamons Any suggestions?
I have a small game I work on occasionally that has a macOS version using skiasharp. Maybe that will be helpful to look at as an example:
https://github.com/chamons/legendary-struggle/blob/master/src/LS.Mac/ViewController.cs#L161
It uses the using SkiaSharp.Views nuget and does everything in C# to setup the views.
@ChrisHamons Thanks for the example. I went through it and some other materials, but am still unable to access the viewcontroller from the rest of my code. And hence I am still unable to do a redraw.
As a newcomer to c# I feel I would need a few weeks to get my head around the key OOP concepts and the Xamarin framework. However I am under a huge pressure to deliver and being completely blocked by this, I can't describe my frustration. I'll probably have to drop the whole thing...
The useful bits from @ChrisHamons' really excellent example are here.
and then later is the actual Mac-native Invalidate function
By directly initializing and caching the SKCanvasView in your ViewController you can invoke it later at will (via the NeedsDisplay property).
Chris- the only thing I couldn't find in your project so far (in terms of giving me exactly what I was hoping for) is a robust way to convert between the MouseDown event space in from the NSView and the canvas' pixel space. Is... it as simple as retaining X and subtracting NSView's Y from the canvas' height?
Edit: nevermind it's right there.
Also my apologies for editing this a million times; I cannot get this markdown to format right.
@wtfork Thanks again for your help. Actually I have those very same lines of code in my
ViewController
. The problem is I cannot callInvalidate
from outside of theViewController
because it is a public method and I would need to instantiate a newViewController
object to do so ("An object reference is required to access non-static member..."). And if I instantiate, of course the newViewController
object will not contain the actualSKCanvasView
used by the app.And in turn if I make
Invalidate
static, I cannot access it theSKCanvasView
object from within the method as it is declared public in the view controller.This is what is blocking me right now. I've tried dozens of solutions including access via reflection,
NSApplication.SharedApplication.KeyWindow
and nothing works...I feel like I am missing something really stupid to get it done but don't have a clue of what it might be...
How many view controllers do you anticipate having? And do you want to invalidate at-will, or just X times a second?
It's just one viewcontroller. The one created by default by Visual Studio for Mac.
I would prefer to invalidate it upon receiving new data from an attached hardware device, but I could also go the X times per second way.
In Chris' example there's an NSTimer he sets up in the ViewController that calls Invalidate() every 33 milliseconds- that may work for you (this is slightly edited from his code):
Then you can just call that at the end of your ViewController's ViewDidLoad() method.
Great, that works. Thanks so much @wtfork
And if you had to do it at will. How would you manage?
Well I'm making an art app, so I will only ever need to invalidate the display after processing user input, which I'm doing through the ViewController, so it's easy to just postpend an Invalidate() call at the end of each input event receiver.
I guess in the worst case for you, you could give your ViewController a public static ViewController reference and initialize it with itself so that some other script could call Invalidate on it. Probably not ideal but if you are looking for Quick'n'Dirty...