skiasharp skcanvasview on mac missing invalidate?

jaymannjaymann Member ✭✭

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

Tagged:

Best Answer

Answers

  • wtforkwtfork Member ✭✭

    I am having a similar issue.

  • wtforkwtfork Member ✭✭

    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.

  • jaymannjaymann Member ✭✭

    Thanks @wtfork. I'll give it a try later today. Nonetheless, isn't there a more canonical way to do this within the framework?

  • wtforkwtfork Member ✭✭

    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 :)

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    Consider filing an issue with skiasharp - https://github.com/mono/SkiaSharp/issues

  • jaymannjaymann Member ✭✭

    @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 the SKCanvasView object.

    I guess I need to access the SKCanvasView member object declared as an outlet the within the ViewController class held in the ViewController.designer.cs file (see below). Because I guess that is the one that holds the Surface object that gets passed through the Skiasharp OnPainting event

    {
        [Register ("ViewController")]
        partial class ViewController
        {
            [Outlet]
            SkiaSharp.Views.Mac.SKCanvasView canvasView { get; set; }
    
            void ReleaseDesignerOutlets ()
            {
                if (canvasView != null) {
                    canvasView.Dispose ();
                    canvasView = null;
                }
            }
        }
    }
    

    However, I feel like I don't know a word of what I am talking about, so I might have got it all wrong...

  • wtforkwtfork Member ✭✭

    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.)

  • jaymannjaymann Member ✭✭

    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?

  • jaymannjaymann Member ✭✭

    @ChrisHamons Any suggestions?

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    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.

  • jaymannjaymann Member ✭✭

    @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...

  • wtforkwtfork Member ✭✭
    edited January 10

    The useful bits from @ChrisHamons' really excellent example are here.

                    SKCanvasView Canvas;   <=== this
                    NSTimer Timer;
    
                    public long Frame { get; private set; } = 0;
                    public float Scale => 1;
    
                    public override void ViewDidLoad ()
                    {
                        base.ViewDidLoad ();
    
                        Controller = new LS.UI.GameController (this);
                        Controller.Startup (new FileStorage ());
    
                        Canvas = new CanvasView (View.Frame) { IgnorePixelScaling = true };   <=== and this
                        Canvas.PaintSurface += OnPlatformPaint;
                        ...
    

    and then later is the actual Mac-native Invalidate function

            public void Invalidate ()
            {
                if (Canvas != null)
                    Canvas.NeedsDisplay = true;
            }
    

    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.

  • jaymannjaymann Member ✭✭
    edited January 10

    @wtfork Thanks again for your help. Actually I have those very same lines of code in my ViewController. The problem is I cannot call Invalidate from outside of the ViewController because it is a public method and I would need to instantiate a new ViewController object to do so ("An object reference is required to access non-static member..."). And if I instantiate, of course the new ViewController object will not contain the actual SKCanvasView used by the app.

    And in turn if I make Invalidate static, I cannot access it the SKCanvasView 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.KeyWindowand 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...

  • wtforkwtfork Member ✭✭

    How many view controllers do you anticipate having? And do you want to invalidate at-will, or just X times a second?

  • jaymannjaymann Member ✭✭

    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.

  • jaymannjaymann Member ✭✭

    Great, that works. Thanks so much @wtfork

    And if you had to do it at will. How would you manage?

  • wtforkwtfork Member ✭✭

    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...

Sign In or Register to comment.