mono / xwt

A cross-platform UI toolkit for creating desktop applications with .NET and Mono
MIT License
1.37k stars 241 forks source link

Window handle #403

Open bigpcz opened 10 years ago

bigpcz commented 10 years ago

I'd like to use XWT + OpenTK. I've created the custom Canvas widget, but I'm not sure how to retrieve handle of it.

OpenTK example: http://www.opentk.com/doc/graphics/graphicscontext

IntPtr handle = ?

IWindowInfo wi = Utilities.CreateWindowsWindowInfo(handle); IGraphicsContext context = new GraphicsContext(GraphicsMode.Default, wi); context.MakeCurrent(wi); GL.Clear(ClearBufferMask.ColorBufferBit); context.SwapBuffers();

sevoku commented 9 years ago

You could use Toolkit.CurrentEngine.GetSafeBackend (object) to get the backend of the window, cast it to the backend in use and access the native window through its Window property. But you'll need to reference the backend assembly and the toolkit (e.g. GTK: Xwt.Gtk, gtk-sharp, glib-sharp, atk-sharp).

For Gtk it would look like:

Window w = new Window ();
var wBackend = Toolkit.CurrentEngine.GetSafeBackend (w) as Xwt.GtkBackend.WindowFrameBackend;
IntPtr handle = wBackend.Window.Handle;

But I'm not sure if this handle is the right one. Probably you need some additional code to retrieve the Graphics server handle (Win32, X11,...) from the native window.

Maybe we need some direct access for that purpose. How about IntPtr IBackend.NativeHandle { get; }?

bigpcz commented 9 years ago

The IBackend.NativeHandle would be cool!

I've tried to get the window handle according to your code. It returns a reference but the wrong one. A value bound to GTK GObject.

    protected override void OnDraw(Xwt.Drawing.Context ctx, Rectangle dirtyRect)
    {
        var win = (MainWindow)this.ParentWindow;
        var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(win) as Xwt.GtkBackend.WindowFrameBackend;
        IntPtr handle = wBackend.Window.Handle;

        // throws the exception: System.ArgumentNullException: Must point to a valid window.
        InitOpenTK(handle);
}
sevoku commented 9 years ago

I have never used OpenTk, but according to its API you need the handle of a X11 (NIX) or Windows Window, which has nothing to do with Gtk, or Wpf. Xwt uses Toolkits which run on top of a window server. So a IBackend.NativeHandle (which can be really useful sometimes not only for OpenTK) would give you exactly what you have received, a handle of the native toolkit object (GObject in case of Gtk). On Wpf you would get the handle of System.Windows.Window (could be used directly with OpenTK maybe?). Retrieving a handle of the underlying server window is something completely different and can not be part of Xwt, since a toolkit can be hosted in different window servers (X11/Xorg or Wayland on NIX, Windows is Windows, Cocoa/X11? on Mac). So Xwt doesn't really bother about the server. So knowing how to get the Toolkit window handle, you have to find out how to get the server handle for OpenTK, which is Toolkit and Server specific. If you use Gtk then https://developer.gnome.org/gtk3/stable/platform-support.html and https://developer.gnome.org/gdk3/stable/gdk3-X-Window-System-Interaction.html could be helpful.

bigpcz commented 9 years ago

Well I think Win32 HWND is needed here not GTK handle.

I did a research and found the following:

wBackend.Window.Handle - is GObject Handle.

There is GDK_WINDOW_HWND macro defined in gdkwin32.h:

         #define GDK_WINDOW_HWND(d) (gdk_win32_drawable_get_handle (d))

The function gdk_win32_drawable_get_handle can be found in the library "gtk-dotnet.dll" but the function is not used there.

So I imported it directly to my app by this:

        [DllImport("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr gdk_win32_drawable_get_handle(IntPtr raw);

and called:

        var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(win) as Xwt.GtkBackend.WindowFrameBackend;
        IntPtr handle = gdk_win32_drawable_get_handle(wBackend.Window.GdkWindow.Handle);
        InitOpenTK(handle);

which works!

The similar can be used for WPF backend:

            var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(win) as Xwt.WPFBackend.WindowFrameBackend;
            var hwndSource = System.Windows.PresentationSource.FromVisual(wBackend.Window) as System.Windows.Interop.HwndSource;
            //hwndSource.CompositionTarget.RenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;
            handle = hwndSource.Handle;

The issue I'm struggling with right now is that the window rendered with OpenGL is repaintedt by XWT Window child widget. If I set Window.Content = null, it throws null exception here:

    public Widget Content {
        get {
            return child;
        }
        set {
            if (child != null)
                child.SetParentWindow (null);
            this.child = value;
            child.SetParentWindow (this); <-- child is null, throws null exception
            Backend.SetChild ((IWidgetBackend)BackendHost.ToolkitEngine.GetSafeBackend (child));
            if (!BackendHost.EngineBackend.HandlesSizeNegotiation)
                Widget.QueueWindowSizeNegotiation (this);
        }
    }

That would be cool to get HWND of widget directly, but I don't know how to do it. Something like that:

    var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(widget) as Xwt.GtkBackend.CanvasBackend;

I'd also prefer the method gdk_win32_drawable_get_handle to be inside XWT.

sevoku commented 9 years ago

Every Gtk Widget has a GdkWindow property, which can be used to retrieve the drawable (like you do it for a Gtk.Window). But not all Widgets have own Gdk windows (e.g. labels, where you have to adjust drawing coordinates) and some Gtk Widgets have more then one Gdk window (like a text box or a spin button for example). So if you want to render over several Gtk widgets in a window, you'll have a problem, since most widgets have own Gdk windows (and own native handles) which are drawn over the root window. So the question is what you exactly want to do. If you simply need a rendering area, take a Canvas and grab its backends GdkWindow.

bigpcz commented 9 years ago

Ok, now I got it, changed the code to get HWND from canvas and not from the window, but it has the same effect anyway.

I've created custom widget:

public class OglWidget : Canvas

created and shown xwt window:

oglWidget = new OglWidget();
mainWindow.Content = oglWidget;
mainWindow.Padding = new WidgetSpacing(50, 50, 50, 50);
mainWindow.Width = 300;
mainWindow.Height = 300;
mainWindow.Title = "OpenTK test";
mainWindow.Show();
oglWidget.InitOpenTK(mainWindow);

got the win32 window handle (hwnd) and pass it to OpenTK graphics context in InitOpenTK method:

        IntPtr handle = IntPtr.Zero;

        if (Xwt.Toolkit.CurrentEngine.Type == ToolkitType.Gtk) {
            var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(this) as Xwt.GtkBackend.CanvasBackend;
            handle = gdk_win32_drawable_get_handle(wBackend.Widget.GdkWindow.Handle);
        }
        wi = Utilities.CreateWindowsWindowInfo(handle);
        context = new GraphicsContext(GraphicsMode.Default, wi);

and rendered faces in the OnDraw:

    protected override void OnDraw(Xwt.Drawing.Context ctx, Rectangle dirtyRect)
    {
        context.MakeCurrent(wi);
        // rendering code
        context.SwapBuffers();
    }

When the app is started, the window is shown with white background. Only after a few seconds when OpenTk is initialized, the background is changed to hex color 0xF5F5F5 (this kind of color is not set in my code anywhere). I change the size of the window by a few pixels then to trigger the OnDraw method and it results into this:

opentktest

The Window padding (set to 50) shows rendered gradient and the OglWidget covers rendered gradient with color 0xF5F5F5. I tried to remove the OglWidget background by this:

    public OglWidget()
    {
        var canvasBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(this) as Xwt.GtkBackend.CanvasBackend;
        canvasBackend.Widget.WidgetFlags |= Gtk.WidgetFlags.AppPaintable;
    }

but it didn't help. I'd like to draw only to OglWidget or to whole window without any child but I'm not sure how to do that.

bigpcz commented 9 years ago

With old Window Forms framework it is really simple. You create a window, get its handle and draw. Or create child control, get its handle and draw to it. No problem except for the framework is old and does not support multiple platforms.

With XWT + Gtk it is a problem as you can see. Too much code and even not really working. Therefore I wrote "The IBackend.NativeHandle would be cool!", because I thought that the Window or the Canvas would return win32 window handle (hwnd), not a GTK GObject which is useless for me. And the work with getting handle and importing native library (gdk_win32_drawable_get_handle) would be wired in XWT.Gtk.

bigpcz commented 9 years ago

I can hide the gtk widget finally!

I set paintable flag and turned off the double buffer on the window:

class MainWindow : Window
{
    public MainWindow()
    {
        var w = this.BackendHost.ToolkitEngine.GetSafeBackend(this) as Xwt.GtkBackend.WindowFrameBackend;
        w.Window.AppPaintable = true;
        w.Window.DoubleBuffered = false;
    }
}

That removed background of the OglWidget. However the white background after the app start was still there. So I called OpenTK init, rendering method before mainWindow.Show(). The wBackend.Widget.GdkWindow was null, so I simply called wBackend.Widget.Realize();

        if (Xwt.Toolkit.CurrentEngine.Type == ToolkitType.Gtk) {
            var wBackend = Xwt.Toolkit.CurrentEngine.GetSafeBackend(this) as Xwt.GtkBackend.CanvasBackend;
            wBackend.Widget.Realize();
            handle = gdk_win32_drawable_get_handle(wBackend.Widget.GdkWindow.Handle);
        }

When the window is shown, the background is drawn in white and then immediately redrawn with content of OpenTK. Not sure why the white background is still drawn, but at least a progress.

sevoku commented 9 years ago

The problem here is the EventBox widget used by Gt.Backend.CanvasBackend for drawing. I saw same problems, as I played around with Alpha colors and Gtk. I think the right way would be to implement a Xwt.GlCanvas with appropriate backends for all toolkits (the Gtk backend could use a Gtk.GlWidget for example). But this should be a separate project outside of Xwt, since all libraries would need to reference OpenTK.

sevoku commented 7 years ago

We expose the native window handle though IWindowFrameBackend.NativeHandle now, its not streight forward, but makes things easier. The Gtk backend will return the platform specific native window handle usable in OpenTK. There is still no Window.NativeHandle (one needs to access the backend) because it can break things especially in cross-toolkit environments. The change has been introduced in https://github.com/mono/xwt/pull/596.

ghost commented 7 years ago

Now new version of OpenGL.Net C# But code is not compatibility to new version of Xwt now.

How do I fix? :(

sevoku commented 7 years ago

Could you explain what exactly is not compatible with what? I don't know about OpenGL.Net. OpenTK needs a native window handle to create the surface. Xwt backends expose the handle through Xwt.Backends.IWindowFrameBackend.NativeHandle.

You can get it for example using:

IntPtr handle = ((Xwt.Backends.IWindowFrameBackend)Toolkit.GetBackend(window)).NativeHandle;

or if you have a custom Window (inheriting Xwt.Window), its even simpler with

IntPtr handle = this.BackendHost.Backend.NativeHandle

after the window has been shown/realized.

ghost commented 7 years ago

Thanks for explanation! It works only window, right?

And how does it work with canvas ( not use Xwt.Window just only Xwt.Canvas? )

example: glcontext embeds into canvas or window.

using System;
using OpenGL;
using Xwt;

namespace XwtGame
{
    class GLCanvas : Canvas
    {
        private IntPtr glContext;

        public GLCanvas()
        {
            glContext = BackendHost.Backend.NativeHandle;

            Gl.Viewport(0, 0, Convert.ToInt32(Content.ScreenBounds.Width), Convert.ToInt32(Content.ScreenBounds.Height));
            Gl.Clear(ClearBufferMask.ColorBufferBit);
            Gl.MatrixMode(MatrixMode.Modelview);
            Gl.LoadIdentity();

            Gl.Begin(PrimitiveType.Triangles);
            Gl.Color3(1.0f, 0.0f, 0.0f); Gl.Vertex2(0.0f, 0.0f);
            Gl.Color3(0.0f, 1.0f, 0.0f); Gl.Vertex2(0.5f, 1.0f);
            Gl.Color3(0.0f, 0.0f, 1.0f); Gl.Vertex2(1.0f, 0.0f);
            Gl.End();
        }
    }
}

But It doesn't exist method "NativeHandle" What is solvation if I use ((Xwt.Backends.ICanvasBackend)Toolkit.GetBackend(window)).NativeHandle; and it is same error. How do I fix? Just I use Window MainWindow =? new Window(..); than it works fine but Why does it not show with GL-Viewport?

using System;
using OpenGL;
using Xwt;

namespace XwtGame
{
    class MainApplication
    {

        [STAThread]
        static void Main(string[] args)
        {
            Application.Initialize(Xwt.ToolkitType.Gtk);
            Window MainWindow = new Xwt.Window()
            {
                Title = "Xwt Game",
                Width = 1000,
                Height = 800
            };
            MainWindow.CloseRequested += (o, e) =>
            {
                Application.Exit();
            };

            IntPtr glContext = ((Xwt.Backends.IWindowFrameBackend)Toolkit.GetBackend(MainWindow)).NativeHandle;

            Gl.Viewport(0, 0, Convert.ToInt32(MainWindow.ScreenBounds.Width), Convert.ToInt32(MainWindow.ScreenBounds.Height));
            Gl.Clear(ClearBufferMask.ColorBufferBit);
            Gl.MatrixMode(MatrixMode.Modelview);
            Gl.LoadIdentity();

            Gl.Begin(PrimitiveType.Triangles);
            Gl.Color3(1.0f, 0.0f, 0.0f); Gl.Vertex2(0.0f, 0.0f);
            Gl.Color3(0.0f, 1.0f, 0.0f); Gl.Vertex2(0.5f, 1.0f);
            Gl.Color3(0.0f, 0.0f, 1.0f); Gl.Vertex2(1.0f, 0.0f);
            Gl.End();

            //GLCanvas glCan = new GLCanvas();
            //MainWindow.Content ;
            MainWindow.Show();
            Application.Run();
        }
    }
}