GameWindow Transparency

Hello!
I’m developing a simple game app, I’ve started with coding a splash screen (short scene on startup) & trying to make GameWindow form fully transparent, but not content of that form.

Basically, trying to show image and some text on top of transparent window before running the game.

I’ve read few articles about WindowsExtendedStyles & DesktopWindowsManager & DirectComposition. WsExStyles set transparency to entire window, while DWM seem to work fine, except that GameWindow flickers on Loading event. Tried also setting TransparentKey value, but it leave ugly edges on a picture. I don’t quite understand how MonoGame engine managing windows (is it drawing backbuffer to winForm?? and what is blinking is the form that renders?).

I suppose, it will be easier to create wpf for this purpose, just want to be able to use Conent pipeline and not connecting many libraries.

Is it possible to achieve this goal not trying to connect MonoGame/WPF or MonoGame/WinForm, using default Game class?

Attaching Code

using System;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace xProject
{
    class SplashScreen : Game
    {
        [DllImport("user32.dll")]
        static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll")]
        static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll")]
        static extern bool SetLayeredWindowAttributes(IntPtr hWnd, int crKey, byte bAlpham, int dwFlags);

        [DllImport("dwmapi.dll")]
        static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMargins);

        struct Margins
        {
            public int left, right, top, bottom;
        }
        Margins marg;

        GraphicsDeviceManager GDM;
        SpriteBatch sBatch;
        Texture2D txr;
        SpriteFont sFont;
        Vector2 wndSz, txrOrig, strSz, AdRes;
        int x;
        float trKey, tmElapsed;
        string greetings;
        enum Fade { txrIn, txrOut, strIn, strOut, finish }
        Fade fState;
        System.Windows.Forms.Form f;

        public SplashScreen()
        {
            GDM = new GraphicsDeviceManager(this);
            AdRes = new Vector2(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);
            wndSz = AdRes / 2;
            GDM.PreferredBackBufferWidth = (int)wndSz.X;
            GDM.PreferredBackBufferHeight = (int)wndSz.Y;
            Content.RootDirectory = "Content";

            IntPtr hWnd = Window.Handle;
            f = System.Windows.Forms.Control.FromHandle(hWnd).FindForm();
            f.TopMost = true;
            Window.IsBorderless = true;
            Window.Position = ((AdRes - wndSz) / 2).ToPoint();

            //Setting margins to extend frame
            marg.left = marg.top = 0;
            marg.right = (int)wndSz.X;
            marg.bottom = (int)wndSz.Y;

            DwmExtendFrameIntoClientArea(hWnd, ref marg);

            //Setting ExStyle to Layered & Transparent
            SetWindowLong(hWnd, -20, GetWindowLong(hWnd, -20) | 0x20 | 0x8000);
            SetLayeredWindowAttributes(hWnd, 0, 128, 0x2);

            greetings = "xProject";
            trKey = 0;
            fState = 0;
            x = 2;
        }
        protected override void Initialize()
        {

            base.Initialize();
        }

        protected override void LoadContent()
        {
            sBatch = new SpriteBatch(GraphicsDevice);
            txr = Content.Load<Texture2D>("Wolf");
            sFont = Content.Load<SpriteFont>("SpF");
            txrOrig = new Vector2(txr.Width, txr.Height) / 2;
            strSz = sFont.MeasureString(greetings);
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            base.UnloadContent();
        }

        protected override void Update(GameTime gameTime)
        {
            tmElapsed = (float)gameTime.ElapsedGameTime.TotalSeconds / x;
            if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
            switch ((int)fState)
            {
                case 0: if (trKey < 1) trKey += tmElapsed; else fState++; break;
                case 1: if (trKey > 0) trKey -= tmElapsed; else fState++; break;
                case 2: if (trKey < 1) trKey += tmElapsed; else fState++; break;
                case 3: if (trKey > 0) trKey -= tmElapsed; else fState++; break;
                case 4: break;
            }
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Transparent);
            sBatch.Begin();
            switch ((int)fState)
            {
                case 0:
                case 1:
                    sBatch.Draw(txr, wndSz / 2 - txrOrig, Color.White * trKey); break;
                case 2:
                case 3:
                    sBatch.DrawString(sFont, greetings, (wndSz - strSz) / 2, Color.White * trKey); break;
            }
            sBatch.End();
            base.Draw(gameTime);
        }
    }
}

^ Drawing some image and text in SpriteBatch ^

Flickering_gif

I hope I make it clear, any advice with be appreciated… Thank you :open_mouth:

Hey, I really have no idea how MonoGame interacts with the main form… but I know when I’m doing animations and such on WinForms I generally have to set the DoubleBuffered property to true. There’s a WindowStyle thinger for that I think, which I’m sure it just does in the background, but I don’t know off-hand what it is.

Aaaaanyway, when it’s off, stuff looks an awful lot like what you’re seeing here. Might be something to look into?

Oh also, side note… when you’re using those Windows API calls, it’s generally helpful to make some kind of enum that refers to the parameters. Fortunately, someone has already made them for you, check it out!

https://www.pinvoke.net/default.aspx/Enums/WindowLongFlags.html

If you haven’t been to this wonderful site yet, it can really help out a ton when mucking about with the Windows API stuff :wink:

Thank you for quick response! :slight_smile:

…set the DoubleBuffered property to true…

The issue is that I cannot call SetStyle method in order to change DoubleBuffered property of the window, it is just not accessible from default Game Class, in order to do it - my class need to inherit Form or Control class, which I’m trying to avoid if possible :sweat:

Is it required of me to create my own winform and render content by the means of MonoGame? If so, I don’t know what is the best way to implement MonoGame functionality in WinForm. I’ve seen XNA examples on MSDN, though I don’t need buttons & another GUI stuff from WinForm, just trying to make GameWindow background fully transparent.

…it’s generally helpful to make some kind of enum that refers to the parameters…

Yes, you’re right, I’m aware of this, just tried to set some particular properties in my case, anyway ExWinStyle set transparency to entire window including it’s content, so I cannot work it through :slight_frown:

Hey, so I poked around and you’re right! You apparently can’t access SetStyle or DoubleBuffered directly; however, you can use some reflection tom-foolery to accomplish what you want. I was able to set DoubleBuffered to True using this code…

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            this.IsMouseVisible = true;

            System.Windows.Forms.Form form = (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(this.Window.Handle);

            PropertyInfo doubleBufferedProperty = typeof(System.Windows.Forms.Form).GetProperty("DoubleBuffered", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic);
            doubleBufferedProperty.SetValue(form, true);
        }

Keep in mind that I have no idea if this will fix it, it’s just a thing to try :slight_smile:

One question though… since this is a splash screen, does it really need to be driven by MonoGame? You could do this via straight WinForms and, when the splash screen is over, have it launch your game. Reading above you mentioned wanting to have the content pipeline as a driver for wanting to use MonoGame, but if all you need is the image, you can just import that since it would only be the one.

I just grabbed your code and tried it myself. I actually don’t see any flickering at all, so when I toggle on DoubleBuffered, I can’t see a difference.

I’m currently trying to run code with doublebuffering.

I can’t see a difference

I don’t see any difference also, form still flickers :upside_down:

I’m running game in VS 2017, Windows 10 on my laptop, maybe it flickers to swiftly for you ??
I don’t know why the form draw itself white color for 1s, strange… :rolling_eyes:

Oh, is it that initial full white rectangle that draws, then it goes? I saw actual flickering throughout the animation in your gif and that’s what I thought you meant.

Yea, I see the same thing you do. Let me play around a bit.

Yes, it’s what I meant. I’ve commented all code on Draw except GraphicsDevice.Clear(), form doesn’t flicker, but as soon as I uncomment spriteBatch.Begin() & End() (spriteBatch.Draw() still commented), again I see white blinking :neutral_face:

Also if it means anything, when I’m commenting GraphicsDevice.Clear(Color.Transparent), so BackBuffer is not refreshed & filled with alpha, I see the following: window flickers and then it is filled with black…

It looks like it’s actually happening on the first GraphicsDevice.Clear(Color.Transparent) call. If I just sick a return at the start of the Draw method it draws a black rectangle instead… I’m assuming this is the default colour. It’s like the buffer doesn’t know how to draw a transparent window until the first frame has drawn.
NOTE: I saw your edit pop up just as I was typing this haha, we did the same thing.

This behaviour actually seems like it might be a bug.

I tried hiding the form on the first draw, then showing it again on the second, but it needs that first transparent draw to occur for some reason.

Oh that’s interesting. If you don’t do anything after the clear, you don’t see the white rectangle. If all you do is a begin/end on the spritebatch, you do see it.

Yeah, I noticed that, but then I turned off DoubleBuffered and it continued to blink.

So, I’m experimenting with it right now. I figure out, when I turn on fullscreen option - it is ok, but if I do the same by setting window bordeless & stretching to fullscreen - then it flickers again)) Something might be connected to GraphicsDeviceManager behaviour on Drawing.

I lied, apparently. This happens now and again, but not consistently. When I posted this, it happened to occur twice in a row so I thought it was a thing… but it’s not. Sorry! :frowning:

As I said above, this might be a bug/race condition in MonoGame. Either way, I’ve exhausted the ideas I have.,… sorry I couldn’t help! Maybe one of the devs around here might have some thoughts.

For the short-term, I would recommend just going with a launcher form and bypassing MonoGame entirely, then come back to this at a later date.

Good luck!

Thank you) Yeah, I will put aside for a while))

Try to change:
f = System.Windows.Forms.Control.FromHandle(hWnd).FindForm();
to
f = (Form)Control.FromHandle(this.Window.Handle);

Its works!