Changing form size from a WM_SIZE message - c#

I have a form with a minimum height set, because I don't want it resized beyond a certain point when it is in a "minimalistic display" mode.
When a user attempts to maximize the window by Aero Snapping it to the top of the screen, the window gets maximized, but the height of the window is only 240 pixels (the set maximum size). If I attempt to handle the WM_SIZE message when the wParam is SIZE_MAXIMIZED, any attempt to set the height of the form is bypassed.
Currently I am handling SC_MAXIMIZE to detect when the maximize button is pushed, and WM_NCLBUTTONDBLCLK to maximize the window if the user double clicks the title bar. In both of these situations, I can toggle the extended window mode and set the minimum size such that it will be able to go full screen.
Of course neither of these messages are posted if the window is maximized via ShowWindow(SW_MAXIMIZE) or when aero-snapped to the top of the screen.
Is there another message I can handle that might occur just before the system actually does the maximization so I can adjust the window size and display mode before hand?
Current Code:
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0112) { // WM_SYSCOMMAND
if (m.WParam == new IntPtr(0xF030)) { // Maximize event - SC_MAXIMIZE from Winuser.h
// The window is being maximized
this.MaximumSize = new Size(9999, 9999);
ToggleDeviceDisplay(true);
linkToggleDeviceList.Visible = false;
}
} else if (m.Msg == 0x00A3) { // WM_NCLBUTTONDBLCLK - Double clicking on window title bar, min or max
if (this.WindowState == FormWindowState.Normal) {
if (grpDeviceList.Visible == false) {
this.MaximumSize = new Size(9999, 9999);
ToggleDeviceDisplay(true);
}
this.WindowState = FormWindowState.Maximized;
linkToggleDeviceList.Visible = false;
} else {
this.WindowState = FormWindowState.Normal;
linkToggleDeviceList.Visible = true;
}
return;
} else if (m.Msg == 0x0005) { // WM_SIZE
if (m.WParam == new IntPtr(0x02)) { // SIZE_MAXIMIZED
// CANT GET WINDOW TO GO TO FULL-SCREEN FROM HERE
this.MaximumSize = new Size(9999, 9999);
// THE LINE BELOW DOESN'T WORK, probably because it is already being sized
this.Height = Screen.FromHandle(this.Handle).WorkingArea.Size.Height;
} else if (m.WParam == new IntPtr(0x00)) { // SIZE_RESTORED
linkToggleDeviceList.Visible = true;
}
}
base.WndProc(ref m);
}
If the window is already in extended display mode when WM_SIZE maximize is sent, there is no problem because the max window size is set to allow full screen, however, if they attempt to maximize from the minimal mode, I can't get the app to switch to take up the full screen during the message.
I know I could trigger a timer or something to run from the message so it would resize so quickly the user wouldn't notice it wasn't full screen right away, but that is just a terrible hack.
EDIT:
To illustrate the two window states, I have uploaded two screenshots here. The top image shows the extended display, which has no limits on window size, the bottom image shows the minimal display, which has a height restriction set so they can't increase the height of the window as all it would do is show more empty space.
Thanks.

It seems to me like you're trying to do a simple thing in an overly complicated way. I would process the WM_GETMINMAXINFO message, which is sent to your window whenever its size or position is about to change. Handling this message gives you the opportunity to specify maximal or minimal values for each of these attributes, effectively preventing it from being made smaller or larger than you desire.
I won't post a lot of sample code because your question indicates you already know how to override WndProc and handle window messages. The only thing you'll need to do is define the MINMAXINFO structure in managed code. Something like this:
[StructLayout(LayoutKind.Sequential)]
struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
Use Marshal.PtrToStructure to convert the pointer contained in the Message.LParam property to an instance of the MINMAXINFO structure defined above. So, inside of your WndProc method, you'd do something like:
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(msg.LParam, typeof(MINMAXINFO));
Update:
From the screenshots you've posted, it looks like the two different displays are identical, the only difference is whether the DataGridView at the bottom is shown. The GroupBox at the top is shown, regardless of the form's size.
Thus, it seems to me that the solution is simply to handle the Form.Resize event (which should be raised regardless of how your form is resized, whether via manually dragging its borders, clicking the caption bar buttons, or with Aero Snap).
Inside of that event handler method, check the current dimensions of your form. If it is sufficiently large, set the DataGridView control's Visible property to true. If it is not of sufficient size, switch to "minimal mode" by setting DataGridView.Visible = false.
It's not a very technically complicated solution, but it seems like it should accomplish all of your desired goals. The motivation as I understand it is just to provide a simpler interface when the form is too small to be able to see everything, and expand that interface when the form is larger. If you handle the Resize event and check the actual size of the form after that event has fired, you can't be wrong.
An alternative solution is just to enable the AutoScroll property and always show both controls. All the user will have to do is scroll up or down to see whatever they want. WinForms takes care of the rest.

Do I understand correctly: you want your window to have minimum allowed size and be able to be maximized? If that is so your code is far too complex and actually not necessary. Just use window's property MinimumSize.

So you want your form to have minimum size, maximum size and still be able to maximize it using title bar double click, maximize button and Aero snap? Tricky :-) Here is the solution.
Set you MinimumSize in properties and then write 2 events:
private void Form1_Resize(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Normal)
{
MaximumSize = new Size(maxWidth, maxHeight);
}
}
and:
private void Form1_ResizeEnd(object sender, EventArgs e)
{
MaximumSize = new Size(0, 0);
}
That will do the trick. At least works on my machine :-)

Related

How to set initial color of owner drawn control

Scenario
Having a Windows Forms Form-derived form that contains a Panel-derived control:
The form gets a black background color set:
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
And the panel control is configured to be double buffered and the like, as described here, here and here:
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
The actual drawing is done inside these overrides in MyPanel:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
Wrong behaviour
Every now and then, when the form is initially shown, my owner drawn panel is shortly drawn as white, before my own drawing code is actually called:
After my drawing code is called, everything is drawn correctly and I never can reproduce the white background again:
Even resizing the window and panel does not make it flicker in white.
Enforcing the wrong behavior
I can enforce the initial white-drawn of my panel, if I put a sleep in the Shown event handler of my form:
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
The panel is shown in white for 1000 ms just before my owner-drawn paint code is being called.
My question
How can I avoid the initial white displaying when having an owner drawn/custom drawn panel?
My goal is to have the control "know" its initial background color (black in my example) right from the start, not after it is initially shown.
Some thoughts
I've tried to play with all kind of things including the CreateParams property, but with no visible success.
My initial idea was to provide some initial background color through the WNDCLASSEX structure, but after digging through the Reference Source, I still have no clue whether this is possible and would help.
Whole code
Just to be safe, following is my whole code.
MyForm.cs:
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
}
MyPanel.cs:
public class MyPanel : Panel
{
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
}
I had a custom control, pretty much completely self-drawn, that was dynamically getting added to a Form in the dropdown mechanism in PropertyGrid.
If I read your problem right, it's basically the same overall issue.
I had BackColor set and was fine. But with DoubleBuffer set, it seems to just ignore it for a bit.
Taking in all of the existing comments on the question, I was able to have this solution, and hope the details help someone else make their own.
Problem 1
My control flickered unless I repainted whole control every OnPaint.
If did proper painting only attempting to paint things that intersected the e.ClipRectangle, then it would flicker in real-time, like effects from invalidates that had to do when the mouse was moved. Attempt to Paint whole thing, no problem.
I watched trace output real-time and watched all of my draws and invalidates print, and never a time where should be introducing flicker myself, on myself directly.
Problem 2
If I turn on DoubleBuffered, then instead it flickered badly as the control was shown every time opening the dropdown. From white background only for 100-200 ms, at least, then to black and rendered foreground suddenly in one step.
Problem 3
I never actually needed double buffer. Both the problem 1 and 2 were always related to WM_ERASEBKGND.
The actual original flicker seems to be caused by WM_ERASEBKGND very briefly visibly whacking my already painted thing, right before I painted it again. Did not really need actual double buffering in my case. For some reason when I was blindly painting the whole list maybe the timing was different and was painting over the erase before could see it.
All that said, if I turn DoubleBuffered on which removes WM_ERASEBKGND via turning on AllPaintingInWmPaint, then initial background won't be painted until I suppose the double buffer and paint process works its way, all the way through the first time.
Problem 4
If I let the WM_ERASEBKGND "just happen", then it's still double painting, and I don't know if or when it might end up flicking anyway for someone else.
If I only turn on SetStyle(OptimizedDoubleBuffer, then I now know I'll be letting the initial background paint and not flicker on show. But I also know I'm using double buffer to mask the WM_ERASEBKGND for the entirety of the life of the control after it is shown.
So....
I did something like this:
Part 1
if the user of the control sees a need to double buffer doing something that might flicker, create a way for them to easily enable it, without forcing AllPaintingInWmPaint. Like if they want to use Paint or a DrawXXX event and doing something that animates or something related to mouse movement.
bool _isDoubleBuffer;
[Category("Behavior")]
public virtual bool DoubleBuffer
{
get { return _isDoubleBuffer; } // dont care about real SetStyle state
set
{
if (value != DoubleBuffer)
{
_isDoubleBuffer = value;
SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
}
}
}
Part 2
Manage WM_ERASEBKGND yourself, as the choice is otherwise 1) always off with AllPaintingInWmPaint, and no background paint on show, or 2) violating what double buffer expects where it would be always masking the WM_ERASEBKGND.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
if (_hasPaintForeground && _isDoubleBuffer)
return;
}
base.WndProc(ref m);
}
Part 3
You are now your own decider of what AllPaintingInWmPaint means.
In this case would want the initial messages to process like normal. When we knew for sure the .Net and DoubleBuffer side was finally kicking it, by seeing our first real paint happen, then turn WM_ERASEBKGND off for the duration.
bool _hasPaintForeground;
protected override void OnPaint(PaintEventArgs e)
{
// your paint code here, if any
base.OnPaint(e);
if (!_hasPaintForeground) // read cheaper than write every time
{
_hasPaintForeground = true;
}
}
Part 4
In my case, I also had originally gimped the OnBackground draw, which works if you are opaque drawing each element yourself in OnPaint. This allowed me to not have double buffer on for so long until I started following the clip and changed the timing so that I started also seeing the other WM_ERASEBKGND side effects and issues.
protected override void OnPaintBackground(PaintEventArgs e)
{ // Paint default background until first time paint foreground.
if (!_hasPaintForeground) // This will kill background image but stop background flicker
{ // and eliminate a lot of code, and need for double buffer.
base.OnPaintBackground(e);
} // PaintBackground is needed on first show so no white background
}
I may not need this part anymore, but if my DoubleBuffer is off then I would. So long as I'm always painting opaque in OnPaint covering the whole draw Clip area.
Addendum:
In addition to all of that....
Separate issue with text render.
It looks like if I render only 250 x 42, like two rows and two text renders, which all occur in one OnPaint, verified with Diagnostics.Trace.WriteLine, then the text renders at least one monitor frame later, every single time. Making it look like the text is flashing. Is just 2x paint background single color then 2x paint text each for rows.
However, if I attempt to paint the whole client area of like 250 x 512 or whatever, like 17 rows, even though the e.Clip is exactly those two rows, because I'm the one that invalidated it, then no flicker of the text, 0 flicker.
There is either some timing issue or other side effect. But that's 17 chances instead of two, for at least one row to flicker text where the whole background is shown before the text renders, and it never happens. If I try to only render rows that are in the clip area it happens every time.
There is def something going with .Net or Windows. I tried with both g.DrawString and TextRenderer.DrawText and they both do it. Flicker if draw 2, not flicker if attempt to draw 17. Every time.
Maybe has something to do with drawing text near the mouse pointer, when OnPaint comes back too quickly?
Maybe if I draw enough things or OnPaint takes longer to come back, it's doing double buffer anyway? Dunno
So....
It's a good thing I went through this exercise with the original question.
I may choose to just render the whole client every time, but I'll never be able to do it the "right way" without something like my example code above.

Scrolling part of a control (Form or Panel) [duplicate]

I have a Form with the following properties:
Background Image
Scrollable Panel with a transparent background, and Dock = DockStyle.Fill
PictureBox with a large Width and Height which shows scroll bars
Now all controls are set to DoubleBuffered including the form itself. Everything works as expected except when scrolling the Panel for the PictureBox, the form background image scrolls with it repeating itself showing vertical and horizontal tearing although its static image that fits the form's size, and when you stop scrolling it shows properly. This only happens when dragging the scrollbars, if i click on any point in the scrollbars to move it, it shows properly.
As per my understanding Double Buffering should eliminate such cases, but even with double buffering its the same, maybe a little bit better but still its a huge problem when scrolling.
I tried to place all controls inside another panel instead of using form background image and place this panel on the form but it didn't make any difference.
You are doing battle with a Windows system option, named "Show window content while dragging". It is turned on for all modern versions of Windows. Turning it off is not a realistic goal, since it is a system option it affects all windows of all apps. There is no back-door to selectively bypass this option.
With it enabled, the OS optimizes the scrolling of a window. It performs a fast bitblt to move the pixels in the video frame buffer and generates a paint message for only the part of the window that is revealed by the scroll. Like the bottom few rows of pixels when you scroll down. Underlying winapi call is ScrollWindowEx(). Intention is to provide an app with a more responsive UI, a lot less work has to be done to implement the scroll.
You can probably see where this is heading, ScrollWindowEx() also moves the pixels that were painted by the form's BackgroundImage. You can see that. Next thing you see is the side-effect of the optimized paint, it only redraws the part of the window that was revealed. So the moved background image pixels don't get redrawn. Looks like a "smearing" effect.
There is a simple workaround for that, just implement an event handler for the panel's Scroll event and call Invalidate(). So the entire panel gets redrawn again:
private void panel1_Scroll(object sender, ScrollEventArgs e) {
panel1.Invalidate();
}
But now you'll notice the side-effect of the paint no longer being optimized. You still see the pixels getting moved, then overdrawn. How visible that is depends a great deal on how expensive the BackgroundImage is to draw. Usually never cheap because it doesn't have the optimal pixel format (32bppPArgb) and doesn't have the right size so needs to be rescaled to fit the window. The visual effect resembles the "pogo", rapid jittering on one edge of the panel.
Pretty unlikely you'll find that acceptable or want to do the work to optimize the BackgroundImage. Stopping ScrollWindowEx() from doing its job requires a pretty big weapon, you can pinvoke LockWindowUpdate(). Like this:
using System.Runtime.InteropServices;
...
private void panel1_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.First) {
LockWindowUpdate(this.Handle);
}
else {
LockWindowUpdate(IntPtr.Zero);
panel1.Update();
if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWindowUpdate(IntPtr hWnd);
Works pretty well, the background image pixels are now rock-steady. Any other pixels, well, not so much. Another visual effect, lets call it a "wrinkle". Getting rid of that artifact can be done by putting the window into compositing mode. Which double-buffers the entire window surface, including the child controls:
protected override CreateParams CreateParams {
get {
const int WS_EX_COMPOSITED = 0x02000000;
var cp = base.CreateParams;
cp.ExStyle |= WS_EX_COMPOSITED;
return cp;
}
}
Only remaining artifact is the side-effect of this not being very cheap code. It probably doesn't look that smooth when you scroll. Which otherwise tells you why windows were designed to be opaque 28 years ago.
It is not easy but it is doable and the following worked fine for me though took me 2 hours to discover:
First you need to make sure the column gets null value without the default 'null' icon, before add it to the grid:
DataGridViewImageColumn imagecol = new DataGridViewImageColumn { ImageLayout = DataGridViewImageCellLayout.Stretch };
imagecol.DefaultCellStyle.NullValue = null;
grid.Columns.Add(imagecol);
Then you need to delete this column value for all rows within ANY event that resizes or moves the rows of that particular column (the example here is for scroll event):
private void DataGridViewScrollEventHandler(object sender, ScrollEventArgs e)
{
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
DataGridView grid = (DataGridView)sender;
foreach (DataGridViewRow row in grid.Rows)
{
row.Cells[1].Value = null;
}
}
}
Finally you need to fill the image value again for all rows you delete it at paint event:
private void DataGridViewPaintEventHandler(object sender, PaintEventArgs e)
{
DataGridView grid = (DataGridView)sender;
foreach (DataGridViewRow row in grid.Rows)
{
row.Cells[1].Value = myImage;
}
}
If you have a lot of rows you need to do this for the visible rows only for performance. There is a property for this so it would be doable.
The best solution is to set the form's background image again on the control scroll event
private void panel1_Scroll(object sender, ScrollEventArgs e) {
/*
Your Code if any exists
*/
//reset the form's background image again in the scroll event
this.BackgroundImage = Properties.Resources.your_background_image;
}

How to set height of the Windows Form forcefully?

I want to set windows form height when I maximize form. I have used Resize Event.
Resize event call automatically when I minimize/restore windows.
Following is my code.
int FormHeight = 260;
private void Form1_Resize(object sender, EventArgs e)
{
this.height = FormHeight;
}
Above code is working fine if size is change when form is open on screen. But when I minimize form, form height is 28px and I can't change it by this.height = FormHeight;
How can I assign this.height forcefully?
Use the MaximumSize property. If you want to restrict the hight only, use it like this:
MaximumSize = new Size(Int32.MaxValue, 260);
You cannot. When the form is minimized, it kind of stops existing for some purposes - for example, it no longer has any client area. That's why the form height is 28px in your case - only the borders actually "exist".
This is just a part of the complex system that handles window rendering - you cannot draw to the form when it's minimized, you can't capture it's contents (remember, there is no client area)...
Raymond has great posts on the topic on his blog - for example, Where did windows minimize to before the task bar was invented? and Why do minimized windows have an apparent size of 160x31?, as well as Obtaining a window's size and position while it is minimized.

Textbox property

I am writing a .net program using C#. I would like to ask how could I let the user resize
the Textbox, so that he could enlarge or diminish the textbox
This is very easy to do in Winforms, it takes but a few lines of code. Every window in Windows has the innate ability to be sizable by the user. This is normally only done for a top-level window (a form) but it works just as well for any client window.
The key is to respond to the WM_NCHITTEST message. Which is a message that Windows sends to a window when you click on it. It essentially asks "what was hit?" You can simply say "the corner was hit" and then Windows takes it from there. It changes the cursor to indicate that the corner can be dragged. And automatically resizes the window when the user moves the mouse.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto a form and set its Multiline property to true. Press F5 and drag the lower-right corner of the textbox to see it work.
using System;
using System.Drawing;
using System.Windows.Forms;
class SizeableTextBox : TextBox {
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// Intercept WM_NCHITTEST
if (m.Msg == 0x84 && this.Multiline) {
// Find out where the cursor is located
var pos = PointToClient(new Point(m.LParam.ToInt32()));
// Return HTBOTTOMRIGHT if in the lower-right corner
if (pos.X >= this.Width - 12 && pos.Y >= this.Height - 12) m.Result = (IntPtr)17;
}
}
}
There isn't a native property for textboxes that allow you to resize them manually. What you can do is set the dock or anchor property so that when the user resizes the form it will resize the textbox with it.

C# - Create a styled winforms like in Windows 7

I would like to create a winform like this:
I already accomplished the visual effect (like as seen in the picture), by following other question. But I can't disallow resizing the form, since to have the border, it must be "Sizeable". Someone suggested putting Minimum Size and Maximum Size values equal to the current Form Size. This solves part of the issue, but when the mouse hovers the border, it still shows the double-ended arrow, suggesting the form is resizeable. Is there any way of disable this cursor change? My goal is to mimic the original systray popups in Windows 7, like the network, sound, etc.
Thank you!
Example code:
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCHITTEST:
m.Result = (IntPtr)HTCLIENT;
return;
}
base.WndProc(ref m);
}
This way, when the cursor hovers the borders, the pointer doesn't change, because it's treated as if it was inside the form, achieving the desired effect.
Add a message handler to your form and handle WM_NCHITTEST. When the original returns HTSIZE (etc.), return HTNONE or HTCAPTION.
Something like this question should get you started.
To explain:
When Windows wants to know which cursor to use for your window, it first sends you a WM_NCHITTEST message (non-client hit test). This message is handled by the WndProc method. Your window is supposed to return one of the HT* codes to tell Windows which part of the window the mouse is over. For example, return HTCAPTION for the caption area, HTCLIENT for the client area, or HTSIZENESW for the bottom left sizing corner. The default message handler (calling base.WndProc) deals with this for standard windows.
We don't have a standard window.
What we're trying to do here is ask the original window what the mouse is over. If it returns any of the HTSIZE* values, we want to replace that return value with HTNONE (for no action) or HTCLIENT (if you want the cursor to be treated as inside the window -- probably not this one) or HTCAPTION (if you want to be able to drag the window by the edges -- might be useful).

Categories