C# - Create a styled winforms like in Windows 7 - c#

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

Related

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.

Changing form size from a WM_SIZE message

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

Telling a Separate Window to Scroll

I need to be able to programmatically scroll a window up and down given only a point on a screen. I've managed to retrieve a handle using Windows API, but I am unable to get it to scroll up or down.
Assume the following code:
//retrieves the correct window.
IntPtr hWnd = Win32.WindowFromPoint(new Point(xPos, yPos));
Win32.Rect rect = default(Win32.Rect);
//retrieves a rectangle with the desired windows dimensions
Win32.GetWindowRect(hWnd, ref rect);
//Insert scroll code here...
to scroll a window you need to send it a windows message by calling SendMessage with the appropriate parameters - for full details regarding scrolling and associated messages etc. see MSDN.
UPDATE - as per comments:
Another option might be to call ScrollWindowEx on the hWnd - as per comments calling ScrollwindowEx should NOT be used since it would create an inconsistency between the displayed state and the internal state of the respective window!
Have you tried using SendMessage() function with WM_VSCROLL and WM_HSCROLL messages?
Also check SetScrollInfo (pInvoked version here). Even this post may be helpful to you.

How do I make a WPF window movable by dragging the extended window frame?

In applications like Windows Explorer and Internet Explorer, one can grab the extended frame areas beneath the title bar and drag windows around.
For WinForms applications, forms and controls are as close to native Win32 APIs as they can get; one would simply override the WndProc() handler in their form, process the WM_NCHITTEST window message and trick the system into thinking a click on the frame area was really a click on the title bar by returning HTCAPTION. I've done that in my own WinForms apps to delightful effect.
In WPF, I can also implement a similar WndProc() method and hook it to my WPF window's handle while extending the window frame into the client area, like this:
// In MainWindow
// For use with window frame extensions
private IntPtr hwnd;
private HwndSource hsource;
private void Window_SourceInitialized(object sender, EventArgs e)
{
try
{
if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
{
throw new InvalidOperationException("Could not get window handle for the main window.");
}
hsource = HwndSource.FromHwnd(hwnd);
hsource.AddHook(WndProc);
AdjustWindowFrame();
}
catch (InvalidOperationException)
{
FallbackPaint();
}
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case DwmApiInterop.WM_NCHITTEST:
handled = true;
return new IntPtr(DwmApiInterop.HTCAPTION);
default:
return IntPtr.Zero;
}
}
The problem is that, since I'm blindly setting handled = true and returning HTCAPTION, clicking anywhere but the window icon or the control buttons causes the window to be dragged. That is, everything highlighted in red below causes dragging. This even includes the resize handles at the sides of the window (the non-client area). My WPF controls, namely the text boxes and the tab control, also stop receiving clicks as a result:
What I want is for only
the title bar, and
the regions of the client area...
... that aren't occupied by my controls
to be draggable. That is, I only want these red regions to be draggable (client area + title bar):
How do I modify my WndProc() method and the rest of my window's XAML/code-behind, to determine which areas should return HTCAPTION and which shouldn't? I'm thinking something along the lines of using Points to check the location of the click against the locations of my controls, but I'm not sure how to go about it in WPF land.
EDIT [4/24]: one simple way about it is to have an invisible control, or even the window itself, respond to MouseLeftButtonDown by invoking DragMove() on the window (see Ross's answer). The problem is that for some reason DragMove() doesn't work if the window is maximized, so it doesn't play nice with Windows 7 Aero Snap. Since I'm going for Windows 7 integration, it's not an acceptable solution in my case.
Sample code
Thanks to an email I got this morning, I was prompted to make a working sample app demonstrating this very functionality. I've done that now; you can find it on GitHub (or in the now-archived CodePlex). Just clone the repository or download and extract an archive, then open it in Visual Studio, and build and run it.
The complete application in its entirety is MIT-licensed, but you'll probably be taking it apart and putting bits of its code around your own rather than using the app code in full — not that the license stops you from doing that either. Also, while I know the design of the application's main window isn't anywhere near similar to the wireframes above, the idea is the same as posed in the question.
Hope this helps somebody!
Step-by-step solution
I finally solved it. Thanks to Jeffrey L Whitledge for pointing me in the right direction! His answer was accepted because if not for it I wouldn't have managed to work out a solution. EDIT [9/8]: this answer is now accepted as it's more complete; I'm giving Jeffrey a nice big bounty instead for his help.
For posterity's sake, here's how I did it (quoting Jeffrey's answer where relevant as I go):
Get the location of the mouse click (from the wParam, lParam maybe?), and use it to create a Point (possibly with some kind of coordinate transformation?).
This information can be obtained from the lParam of the WM_NCHITTEST message. The x-coordinate of the cursor is its low-order word and the y-coordinate of the cursor is its high-order word, as MSDN describes.
Since the coordinates are relative to the entire screen, I need to call Visual.PointFromScreen() on my window to convert the coordinates to be relative to the window space.
Then call the static method VisualTreeHelper.HitTest(Visual,Point) passing it this and the Point that you just made. The return value will indicate the control with the highest Z-Order.
I had to pass in the top-level Grid control instead of this as the visual to test against the point. Likewise I had to check whether the result was null instead of checking if it was the window. If it's null, the cursor didn't hit any of the grid's child controls — in other words, it hit the unoccupied window frame region. Anyway, the key was to use the VisualTreeHelper.HitTest() method.
Now, having said that, there are two caveats which may apply to you if you're following my steps:
If you don't cover the entire window, and instead only partially extend the window frame, you have to place a control over the rectangle that's not filled by window frame as a client area filler.
In my case, the content area of my tab control fits that rectangular area just fine, as shown in the diagrams. In your application, you may need to place a Rectangle shape or a Panel control and paint it the appropriate color. This way the control will be hit.
This issue about client area fillers leads to the next:
If your grid or other top-level control has a background texture or gradient over the extended window frame, the entire grid area will respond to the hit, even on any fully transparent regions of the background (see Hit Testing in the Visual Layer). In that case, you'll want to ignore hits against the grid itself, and only pay attention to the controls within it.
Hence:
// In MainWindow
private bool IsOnExtendedFrame(int lParam)
{
int x = lParam << 16 >> 16, y = lParam >> 16;
var point = PointFromScreen(new Point(x, y));
// In XAML: <Grid x:Name="windowGrid">...</Grid>
var result = VisualTreeHelper.HitTest(windowGrid, point);
if (result != null)
{
// A control was hit - it may be the grid if it has a background
// texture or gradient over the extended window frame
return result.VisualHit == windowGrid;
}
// Nothing was hit - assume that this area is covered by frame extensions anyway
return true;
}
The window is now movable by clicking and dragging only the unoccupied areas of the window.
But that's not all. Recall in the first illustration that the non-client area comprising the borders of the window was also affected by HTCAPTION so the window was no longer resizable.
To fix this I had to check whether the cursor was hitting the client area or the non-client area. In order to check this I needed to use the DefWindowProc() function and see if it returned HTCLIENT:
// In my managed DWM API wrapper class, DwmApiInterop
public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam)
{
if (uMsg == WM_NCHITTEST)
{
if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT)
{
return true;
}
}
return false;
}
// In NativeMethods
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
Finally, here's my final window procedure method:
// In MainWindow
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case DwmApiInterop.WM_NCHITTEST:
if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam)
&& IsOnExtendedFrame(lParam.ToInt32()))
{
handled = true;
return new IntPtr(DwmApiInterop.HTCAPTION);
}
return IntPtr.Zero;
default:
return IntPtr.Zero;
}
}
Here's something you could try:
Get the location of the mouse click (from the wParam, lParam maybe?), and use it to create a Point (possibly with some kind of coordinate transformation?).
Then call the static method VisualTreeHelper.HitTest(Visual,Point) passing it this and the Point that you just made. The return value will indicate the control with the highest Z-Order. If that's your window, then do your HTCAPTION voodoo. If it's some other control, then...don't.
Good luck!
Looking to do the same thing (make my extended Aero glass draggable in my WPF app), I just came across this post via Google. I read through your answer, but decided to keep searching to see if there was anything simpler.
I found a much less code-intensive solution.
Simply create a transparent item behind your controls, and give it a left mouse button down event handler which calls the window's DragMove() method.
Here's the section of my XAML which appears over my extended Aero glass:
<Grid DockPanel.Dock="Top">
<Border MouseLeftButtonDown="Border_MouseLeftButtonDown" Background="Transparent" />
<Grid><!-- My controls are in here --></Grid>
</Grid>
And the code-behind (this is within a Window class, and so DragMove() is available to call directly):
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
And that's it! For your solution, you would have to add more than one of these to achieve your non-rectangular draggable area.
simple way is
create stackpanel or every thing you want for your titlebar
XAML
<StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>
code
private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}

How can you get the coordinates of the "X" button in a window?

For one reason or another, I have a need to detect when the user actually clicked on the X button. What I have so far is this:
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)0xa1) //WM_NCLBUTTONDOWN
{
Point p = new Point((int)m.LParam);
p = this.PointToClient(p);
if (p.X > 680)
{
//do what I need to do...
}
}
base.WndProc(ref m);
}
Basically, I look out for the "WM_NCLBUTTONDOWN" message which is the Mouse Click on the Non Client Area of a window. Then, I get the X and Y coordinates from the LParam and finally I convert it to Screen Coordinates. So at this point, I know that the user clicked on the non client area and I know where on the Form.
My question is, how can I tell if these coordinates are on the X button. For now, I'm hardcoding 680 because that's what works in the current size of the window (it's not sizable) but the problem is I'm using Windows 7 which has bigger X buttons than XP, so obviously hardocding isn't a viable option. Furthermore, I haven't even coded for the Y coordinates, so if someone clicks on the right edge of the window, that triggers that code as well. So... anyone have any ideas?
Let's say you have an OK and a Cancel button, why don't you just set a value when one of these buttons is clicked. Then on the form's Closing event, if this value is not set, you know the X button has been clicked. Unless there are other ways of closing the form I'm not aware of...
Edit:
Instead of using a global boolean, you could change the DialogResult property of the form on your button clicks. I'm not sure what is the DialogResult value when you click the X button though, you'll have to try it.
If you test for the WM_NCHITTEST message that should tell you when the mouse is hovering over the close button.

Categories