r/csharp•
CatMerc

How does Windows Forms/WPF create a window?

I've been trying to understand the inner workings of C# and the applications I'm building, and I've come to realize that I don't actually understand how the part of opening a window actually works behind the layers of abstraction? Could you do it manually with enough knowledge of Microsoft's dll's?

My goal is to basically open a window of a certain size, and paint a single pixel onto it, without using Windows Forms, WPF, Monogame, or anything of the sort. Any reading material would be highly helpful!

Posted Jan 27, 2020, 6:32 PM UTC

7134
Slypenslyde•

Windows Forms is more or less a wrapper of GDI, the venerable UI framework Windows has used since I think about the 3.1 era. The best way to learn about how it works is the Petzold book, but a brief summary is:

  • It's not object-oriented. It's a handle-oriented C library. That means a lot of methods you'd think belong to a class in C# are floating around in global scope and expect you to pass a handle (called hWnd after its type, "Handle to Window") to indicate which Window you mean.
  • In API, "Window" and "Control" are synonymous. Buttons are windows. Text boxes are windows. It's sort of like the Control base class of API.
  • Making a form begins by populating a WNDCLASS or WNDCLASS_EX struct. This defines a lot of options like border style we associate with properties of the Form class.
  • You also have to define a special method for it called WndProc, or "Window Procedure". This is the event loop for the form. Its job is to loop, wait for messages, and process them. In .NET terms, this is the thing that raises events when the user does stuff. For example, a left mouse-click sends me messages WM_MOUSEDOWN and WM_MOUSEUP, I can't remember if there's a WM_CLICK but you get the drift.
  • You pass your WNDCLASS struct (which has a pointer to your WndProc to RegisterClassEx(), so Windows knows what options to use if someone requests that kind of window.
  • Later, you call CreateWindowEx(), passing the name you put in your WNDCLASS. Windows allocates memory for your window and gives you an HWND back to represent it. Or it indicates an error happened.
  • Finally, to get the window on the screen, you call ShowWindow(), passing it the HWND you got back.

That's more or less the description of what happens in these lines of C#:

// Creates the "class", registers it, calls CreateWindowEx() and
// stores the HWND.
var f = new MainForm();
// Calls ShowWindow() using that HWND.
f.Show();

You can poke at these within C#. The CreateParams property gives you access to a lot of the members of WNDCLASS_EX; if you override it you can tweak a few things WinForms doesn't give you easy access to. There's also an overridable WndProc() that lets you handle Windows Messages at a lower level than C#, opening the door to handle weird messages MS didn't handle and raise events for.

For WPF, I have no clue. It was created as a .NET API, not COM or C. So it kind of does its own thing. I'm sure there's some native library it calls into somewhere, but MS didn't present it as, "Here's the WPF API and it's .NET Wrapper"; as far as I know that native library is internal and undocumented. I do know it's "windowless", which is a cryptic way to say, "It doesn't use HWNDs, so creating elements is more lightweight and has fewer gotchas". That also means WPF windows don't always play well with "normal" GDI APIs, since they always expect an HWND.

90
DoubleAccretion•

WPF windows (as in top level ones) are HWND based, I am quite sure. Complete with proper naming too!

19
chucker23n•

Yes and no. The window is; its contents are not. In WinForms, any control inside a form is itself a GDI object. In WPF, only a few classes interact with GDI. Everything inside is just drawn with Direct3D, a lot like running a game in a window.

21
DoubleAccretion•

as in top level ones

We agree exactly.

19
chucker23n•

Dammit, didn’t read that. So sorry.

13
Slypenslyde•

Yeah, but an application isn't so much the Window interactions as the hundreds of child window interactions, and child windows in WPF are "windowless".

I found this out the hard way because, years ago, I was on a greenfield team writing an ambitious WPF application with a large suite of our own custom controls (one of our products WAS the controls suite.) I was tasked with making sure our controls supported the brand-new UI Tests MS released, so I started digging into their automation API. It was really thoughtful and had a lot of ways to make crazy controls accessible... if you had an HWND.

I couldn't figure out what to do about WPF controls, so I used our MSDN subscription to get in contact with some MS Engineers on the team too. They were... surprised. Apparently it blindsided them that WPF was windowless, and it turns out WPF support had never been on their radar. No solution.

This is one of many reasons why I am never writing Windows desktop applications with a Microsoft framework again ;)

5
Alikont•

WPF does window creation in a usual way to get top level HWMD, but then creates DirectX surface and attaches it to HWND, like a DirectX game.

Everything inside that window just draws into that DirectX surface.

8
mrGood238•

Great explanation! If I may add, one of (many) limitations of GDI is number of those handles - it sits at 10000 but it can be increased (https://stackoverflow.com/questions/38612364/how-to-increase-the-maximum-amount-of-gdi-object-for-windows-10). Why is this significant? Well, when you exhaust the handles, oldest ones will be destroyed as new ones are created an consequence of that is interesting - GDI objects (labels, textboxes, entire windows) can start disappearing with more or less dangerous errors. So, if you have an application which dynamically creates a large number of controls, you might run into that limit because its global (https://docs.microsoft.com/en-us/windows/win32/sysinfo/gdi-objects). In any case, if you start having this problem, you should probably rethink what are you doing and is the GDI (winforms) solution for you.

6
Slypenslyde•

Yeah, I was a custom controls author for ~6 years and every now and then I'd see someone run into this. Mostly it was people trying to write games and using PictureBox as sprites, but occasionally some poor soul would try to write their own DataGrid using a TextBox per cell.

Nobody liked my advice when I pointed out the only way to accomplish feats like that was to use some combination of:

  • Draw as much as possible to a Bitmap so you aren't using handles.
  • Keep a private, internal, single control that represents the ONE focused input control and move it around as needed.
7
mrGood238•

It's always the PictureBox and whatever insanity someone is trying to accomplish :)

We are currently developing drawing control for some kind of BMS/status control app and if you have any pointers what to avoid, I would be grateful if you shared some of your experience. I'm sure others would also like to read about something like that since winforms are "reborn" with .Net Core.

4
Slypenslyde•

It's hard to come up with some general pointers, the last I touched WinForms was maybe 7 years ago. But I found, in general, people were too hung up on "it's hard" and would avoid the right solution for too long.

WinForms isn't fancy out of the box, and very few controls let you control enough of their rendering to MAKE them fancy. For just about anything but text entry, it's not too hard to write your own version of whatever they're using. For something with text entry, it's more sane to find a way to incorporate a borderless TextBox into your drawing, just make sure it layers above your other stuff.

The other big thing to worry about these days is DPI. Don't just slap controls on a form, get it working on one machine, and call it a day. Make sure your team has access to a high-DPI something like a Surface Pro or even a fancy 4K monitor. You'll be surprised how much your app changes when Windows rescales it. (In general, Windows is REALLY BAD at rescaling). There are articles about how to tell Windows your app handles high DPI and doesn't need help, but they arrived long after I was off the train (I think it has to do with the application manifest? It changed a lot.)

Petzold's book called for handling DPI way back in the 90s and nobody listened. It's not as hard as you'd think, but I'm running out of time so I'll hand-wavy point out: WPF has a unit that is 96 per inch, and on a higher DPI display it changes how many pixels each unit means. Using a coordinate like that internally for custom controls can save your bacon.

But also brush up on your 2D geometry. My controls worked on weird displays because I never used fixed positioning. If I wanted some part to be "10 pixels beneath this header text", then I:

  • Reworked 10 pixels as a buffer variable and adjusted it for DPI to represent a unit of measure like "roughly 3/16 of an inch".
  • Measured the string using MeasureText() before drawing it so I'd know its render size.
  • Draw the next part "buffer pixels beneath headerBounds.Bottom".

It's not really hard once you get used to it, but nobody wants to hear it or do it. I had entire forms that worked on high DPI monitors and resized themselves smartly. It's more work, but so is finding out 3 months after release it doesn't work on an important customer's machine and they need it fixed yesterday.

In the end, if you don't already have a lot of GDI experience, it's probably best to consider buying a controls suite like SyncFusion. They do good stuff, and my hobby projects used to involve trying to copy their controls ;)

6
Reelix•

Mostly it was people trying to write games and using PictureBox as sprites

This was me back in around 2006 - Good times - Good times :p (Moved from PictureBox's to GDI+ draw calls to XNA to ANX (Briefly) to MonoGame (It was very broken) to ThreeJS (Fun times...) to Unity - Where I'm now happy :p)

2
sifow•

What do you think about XNA? Do you think it's a bad thing it got deprecated?

1
CatMercOP•

Very interesting! I ordered programming Windows fifth edition. Thank you for setting me on the right path!

5
jenny_the_pirate•

Pinvoke.net is also helpful. There is also a visual studio extension.

3
kingvolcano•

Ah, this brings back memories. Also setting up the message pump. What was it, those 3 methods?

GetMessage TranslateMessage DispatchMessage

3
intangibleTangelo•

Wow. I haven't written a Windows GUI in almost two decades. It's interesting to learn that the underlying API has remained the same. I had assumed it was replaced with the Win8 UI (Metro? idk, I'm a Linux guy) and now ran a GDI compatibility layer.

2
georgeka•

Wow, I kinda thought the WPF has moved away from the Win32 APIs, It's been a while since I did any window related coding in Win32 and MFC. Back in the days, I learned a lot of the inner workings of Windows through following Raymond Chen's The Old New Thing blog, which is still amazingly alive at https://devblogs.microsoft.com/oldnewthing

2
SpaceGenesis•

Excellent explanation. Take my upvote.

1
Reelix•

You pass your WNDCLASS struct (which has a pointer to your WndProc to RegisterClassEx()), so Windows knows what options to use if someone requests that kind of window.

FTFY (Missing bracket)

1
cryo•

Actually, for Winforms it's primarily USER rather than GDI, but yeah.

1
wasabiiii•

People have been and still are creating Windows applications without .Net.

So, yeah. There's a 30 year history of APIs for this.

The Win32 API predates and underlies .Net. By a lot.

https://docs.microsoft.com/en-us/windows/win32/winmsg/using-windows

12
CatMercOP•

Thanks!

4
tweq•

Windows Forms is a fairly direct wrapper around the Win32 APIs like CreateWindowEx. You can do the same thing with some knowledge of pinvoke. I'd expect most tutorials you'll find are for C++, but they'll at least give you an idea how it works in principle and which methods you need.

12
jonpobst•

There's several open source things you can look at to see what's going on, like Winforms, WPF, Avalonia, etc.

For example, here's the meat of Mono's Winforms implementation that interacts with the OS:

https://github.com/mono/mono/blob/master/mcs/class/System.Windows.Forms/System.Windows.Forms/XplatUIWin32.cs

5
machinarius•

You may start by checking out the Win32 API for a basic window: https://docs.microsoft.com/en-us/windows/win32/learnwin32/creating-a-window - Do note that to invoke this API from c# you need to know how to P/Invoke native APIs, and furthermore, how Win32 APIs work overall. Be ready for a long read.

2
UninformedPleb•

Approximately like this. That's the canonical Win32 Hello World example.

When you use new Form() in C#, you're calling all of that.

1
biggestidiotever2•

I think I'll recreate MFC in C#. Just use the WinAPI to create raw controls, just like this guy wants to do. :)

0
UninformedPleb•

It's super easy to do.

Step 1: using System.Windows.Forms;

There is no step 2.

3
biggestidiotever2•

This misses the whole MFC part. I'm talking about all the predefined classes and such

1
UninformedPleb•

Oh, gotcha. Yeah, you can wrap all of that stuff and do P/Invokes. It's even useful! (Occasionally...) For example, it's the only sane way to get a ribbon control in C#.

1
fuse_changer•

It's non free software so you will never know! Switch to free software to learn

0