How to make my Windows Form app snap to screen edges? - c#

Anyone out there know how to make your .net windows form app sticky/snappy like Winamp so it snaps to the edges of the screen?
The target framework would be .NET 2.0 Windows Form written in C#, using VS08. I am looking to add this functionality to a custom user control, but I figured more people would benefit from having it described for the application and its main form.
Thank you.

This worked pretty well, works on multiple monitors, observes the taskbar:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private const int SnapDist = 100;
private bool DoSnap(int pos, int edge) {
int delta = pos - edge;
return delta > 0 && delta <= SnapDist;
}
protected override void OnResizeEnd(EventArgs e) {
base.OnResizeEnd(e);
Screen scn = Screen.FromPoint(this.Location);
if (DoSnap(this.Left, scn.WorkingArea.Left)) this.Left= scn.WorkingArea.Left;
if (DoSnap(this.Top, scn.WorkingArea.Top)) this.Top = scn.WorkingArea.Top;
if (DoSnap(scn.WorkingArea.Right, this.Right)) this.Left = scn.WorkingArea.Right - this.Width;
if (DoSnap(scn.WorkingArea.Bottom, this.Bottom)) this.Top = scn.WorkingArea.Bottom - this.Height;
}
}

The accepted answer only snaps the window after finishing the drag, whereas I wanted the form to continuously snap to the screen edges while dragging. Here's my solution, loosely based off the Paint.NET source code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Whatever
{
/// <summary>
/// Managed equivalent of the Win32 <code>RECT</code> structure.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct LtrbRectangle
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public LtrbRectangle(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public Rectangle ToRectangle()
{
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
public static LtrbRectangle FromRectangle(Rectangle rect)
{
return new LtrbRectangle(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
}
public override string ToString()
{
return "{Left=" + Left + ",Top=" + Top + ",Right=" + Right + ",Bottom=" + Bottom + "}";
}
}
/// <summary>
/// A form that "snaps" to screen edges when moving.
/// </summary>
public class AnchoredForm : Form
{
private const int WmEnterSizeMove = 0x0231;
private const int WmMoving = 0x0216;
private const int WmSize = 0x0005;
private SnapLocation _snapAnchor;
private int _dragOffsetX;
private int _dragOffsetY;
/// <summary>
/// Flags specifying which edges to anchor the form at.
/// </summary>
[Flags]
public enum SnapLocation
{
None = 0,
Left = 1 << 0,
Top = 1 << 1,
Right = 1 << 2,
Bottom = 1 << 3,
All = Left | Top | Right | Bottom
}
/// <summary>
/// How far from the screen edge to anchor the form.
/// </summary>
[Browsable(true)]
[DefaultValue(10)]
[Description("The distance from the screen edge to anchor the form.")]
public virtual int AnchorDistance { get; set; } = 10;
/// <summary>
/// Gets or sets how close the form must be to the
/// anchor point to snap to it. A higher value gives
/// a more noticable "snap" effect.
/// </summary>
[Browsable(true)]
[DefaultValue(20)]
[Description("The maximum form snapping distance.")]
public virtual int SnapDistance { get; set; } = 20;
/// <summary>
/// Re-snaps the control to its current anchor points.
/// This can be useful for re-positioning the form after
/// the screen resolution changes.
/// </summary>
public void ReSnap()
{
SnapTo(_snapAnchor);
}
/// <summary>
/// Forces the control to snap to the specified edges.
/// </summary>
/// <param name="anchor">The screen edges to snap to.</param>
public void SnapTo(SnapLocation anchor)
{
Screen currentScreen = Screen.FromPoint(Location);
Rectangle workingArea = currentScreen.WorkingArea;
if ((anchor & SnapLocation.Left) != 0)
{
Left = workingArea.Left + AnchorDistance;
}
else if ((anchor & SnapLocation.Right) != 0)
{
Left = workingArea.Right - AnchorDistance - Width;
}
if ((anchor & SnapLocation.Top) != 0)
{
Top = workingArea.Top + AnchorDistance;
}
else if ((anchor & SnapLocation.Bottom) != 0)
{
Top = workingArea.Bottom - AnchorDistance - Height;
}
_snapAnchor = anchor;
}
private bool InSnapRange(int a, int b)
{
return Math.Abs(a - b) < SnapDistance;
}
private SnapLocation FindSnap(ref Rectangle effectiveBounds)
{
Screen currentScreen = Screen.FromPoint(effectiveBounds.Location);
Rectangle workingArea = currentScreen.WorkingArea;
SnapLocation anchor = SnapLocation.None;
if (InSnapRange(effectiveBounds.Left, workingArea.Left + AnchorDistance))
{
effectiveBounds.X = workingArea.Left + AnchorDistance;
anchor |= SnapLocation.Left;
}
else if (InSnapRange(effectiveBounds.Right, workingArea.Right - AnchorDistance))
{
effectiveBounds.X = workingArea.Right - AnchorDistance - effectiveBounds.Width;
anchor |= SnapLocation.Right;
}
if (InSnapRange(effectiveBounds.Top, workingArea.Top + AnchorDistance))
{
effectiveBounds.Y = workingArea.Top + AnchorDistance;
anchor |= SnapLocation.Top;
}
else if (InSnapRange(effectiveBounds.Bottom, workingArea.Bottom - AnchorDistance))
{
effectiveBounds.Y = workingArea.Bottom - AnchorDistance - effectiveBounds.Height;
anchor |= SnapLocation.Bottom;
}
return anchor;
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WmEnterSizeMove:
case WmSize:
// Need to handle window size changed as well when
// un-maximizing the form by dragging the title bar.
_dragOffsetX = Cursor.Position.X - Left;
_dragOffsetY = Cursor.Position.Y - Top;
break;
case WmMoving:
LtrbRectangle boundsLtrb = Marshal.PtrToStructure<LtrbRectangle>(m.LParam);
Rectangle bounds = boundsLtrb.ToRectangle();
// This is where the window _would_ be located if snapping
// had not occurred. This prevents the cursor from sliding
// off the title bar if the snap distance is too large.
Rectangle effectiveBounds = new Rectangle(
Cursor.Position.X - _dragOffsetX,
Cursor.Position.Y - _dragOffsetY,
bounds.Width,
bounds.Height);
_snapAnchor = FindSnap(ref effectiveBounds);
LtrbRectangle newLtrb = LtrbRectangle.FromRectangle(effectiveBounds);
Marshal.StructureToPtr(newLtrb, m.LParam, false);
m.Result = new IntPtr(1);
break;
}
base.WndProc(ref m);
}
}
}
And here's what it looks like:

Just retrieve the current pixel height/width of the monitor you're on...
How to determine active monitor of the current cursor location
... and process the location changed/moved events for the form. When you get within, say 25 pixels or so of an edge (your main form's Location.Left + form width) or height (your main form's Location.Top + form height), then go ahead and set the .Left and .Top properties so that your application "docks" in the corners.
Edit: One other note - when you actually do the "snapping" you may also want to move the cursor position the relative distance to make it stay on the same point on the window bar. Otherwise your form may become a giant ping pong ball between the cursor position and your "snappy" functionality as the MouseMove and form location changed events fight against each other.

I don't know if you found your solution, but I created a small component for just that: http://www.formsnapper.net - it snaps accross the process boundaries!

https://github.com/stax76/staxrip
Protected Overrides Sub WndProc(ByRef m As Message)
Snap(m)
MyBase.WndProc(m)
End Sub
Private IsResizing As Boolean
Sub Snap(ByRef m As Message)
Select Case m.Msg
Case &H214 'WM_SIZING
IsResizing = True
Case &H232 'WM_EXITSIZEMOVE
IsResizing = False
Case &H46 'WM_WINDOWPOSCHANGING
If Not IsResizing Then Snap(m.LParam)
End Select
End Sub
Sub Snap(handle As IntPtr)
Dim workingArea = Screen.FromControl(Me).WorkingArea
Dim newPos = DirectCast(Marshal.PtrToStructure(handle, GetType(WindowPos)), WindowPos)
Dim snapMargin = Control.DefaultFont.Height
Dim border As Integer
If OSVersion.Current >= OSVersion.Windows8 Then border = (Width - ClientSize.Width) \ 2 - 1
If newPos.Y <> 0 Then
If Math.Abs(newPos.Y - workingArea.Y) < snapMargin AndAlso Top > newPos.Y Then
newPos.Y = workingArea.Y
ElseIf Math.Abs(newPos.Y + Height - (workingArea.Bottom + border)) < snapMargin AndAlso Top < newPos.Y Then
newPos.Y = (workingArea.Bottom + border) - Height
End If
End If
If newPos.X <> 0 Then
If Math.Abs(newPos.X - (workingArea.X - border)) < snapMargin AndAlso Left > newPos.X Then
newPos.X = workingArea.X - border
ElseIf Math.Abs(newPos.X + Width - (workingArea.Right + border)) < snapMargin AndAlso Left < newPos.X Then
newPos.X = (workingArea.Right + border) - Width
End If
End If
Marshal.StructureToPtr(newPos, handle, True)
End Sub

Related

C# Desktop background displaying incorrect location maybe?

Hi I am currently creating an animated desktop background that uses axWindowsMideaPlayer to play video files in a loop I take the WMP and set its handle to the desktop handle, it works great on single display monitor setup but fails with multiple monitors. Why it is failing is because monitors can have their own set position like left/right/topleft/topright/top/bottom/bottomleft and bottom right of the primary screen which puts their position in the negatives and so forth.. My question is how can I position each WMP correctly on each monitor? Here is what I have so far,
this is how I get each monitor...
public class DisplayInfo
{
public bool isPrimary { get; set; }
public int ScreenHeight { get; set; }
public int ScreenWidth { get; set; }
public Rect MonitorArea { get; set; }
public Rect WorkArea { get; set; }
public string DeviceName { get; set; }
}
/// <summary>
/// Collection of display information
/// </summary>
public class DisplayInfoCollection : List<DisplayInfo>
{
// size of a device name string
private const int CCHDEVICENAME = 32;
/// <summary>
/// The MONITORINFOEX structure contains information about a display monitor.
/// The GetMonitorInfo function stores information into a MONITORINFOEX structure or a MONITORINFO structure.
/// The MONITORINFOEX structure is a superset of the MONITORINFO structure. The MONITORINFOEX structure adds a string member to contain a name
/// for the display monitor.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct MONITORINFOEX
{
/// <summary>
/// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFOEX) (72) before calling the GetMonitorInfo function.
/// Doing so lets the function determine the type of structure you are passing to it.
/// </summary>
public int Size;
/// <summary>
/// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates.
/// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
/// </summary>
public Rect Monitor;
/// <summary>
/// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications,
/// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor.
/// The rest of the area in rcMonitor contains system windows such as the task bar and side bars.
/// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
/// </summary>
public Rect WorkArea;
/// <summary>
/// The attributes of the display monitor.
///
/// This member can be the following value:
/// 1 : MONITORINFOF_PRIMARY
/// </summary>
public uint Flags;
/// <summary>
/// A string that specifies the device name of the monitor being used. Most applications have no use for a display monitor name,
/// and so can save some bytes by using a MONITORINFO structure.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string DeviceName;
public void Init()
{
this.Size = 40 + 2 * CCHDEVICENAME;
this.DeviceName = string.Empty;
}
}
[DllImport("user32.dll")]
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
/*
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
*/
/// <summary>
/// Returns the number of Displays using the Win32 functions
/// </summary>
/// <returns>collection of Display Info</returns>
public static DisplayInfoCollection GetDisplays()
{
DisplayInfoCollection col = new DisplayInfoCollection();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData)
{
MONITORINFOEX mi = new MONITORINFOEX();
mi.Size = (int)Marshal.SizeOf(mi);
bool success = GetMonitorInfo(hMonitor, ref mi);
if (success)
{
DisplayInfo di = new DisplayInfo();
di.ScreenWidth = (mi.Monitor.Right - mi.Monitor.Left);
di.ScreenHeight = (mi.Monitor.Bottom - mi.Monitor.Top);
di.MonitorArea = mi.Monitor;
di.WorkArea = mi.WorkArea;
di.isPrimary = Convert.ToBoolean(mi.Flags);
di.DeviceName = mi.DeviceName;
col.Add(di);
}
return true;
}, IntPtr.Zero);
return col;
}
}
this is how I have tried to call it and use it but it puts WMP all over the place depending on where the monitors are positioned.
DisplayInfoCollection dic = DisplayInfoCollection.GetDisplays();
int count = 0;
int totalPosX = 0;
int totalPosY = 0;
DisplayInfo dInfo = null;
List<DisplayInfo> di = dic.OrderByDescending(d => d.isPrimary).ToList();
var or = SystemInformation.VirtualScreen;
foreach (DisplayInfo dm in di)
{
bool zeroOutX = false;
bool zeroOutY = false;
if (dm.isPrimary)
{
totalPosX = or.Left > 0 ? or.Left : -or.Left;
totalPosY = or.Top > 0 ? or.Top : -or.Top;
dInfo = dm;
}
else
{
bool left = false;
bool top = false;
bool right = false;
bool bottom = false;
bool topLeft = false;
bool topRight = false;
bool bottomLeft = false;
bool bottomRight = false;
int posY = dm.MonitorArea.Top > 0 ? dm.MonitorArea.Top : -dm.MonitorArea.Top;
if (dm.MonitorArea.Left < 0)
{
left = true;
}
else
right = dm.MonitorArea.Left > 0;
if (dm.MonitorArea.Top < 0)
{
top = true;
}
else
bottom = dm.MonitorArea.Top > 0;
bool center = (dm.MonitorArea.Left > 0 ?
dm.MonitorArea.Left : -dm.MonitorArea.Left) > 0 ||
(dm.MonitorArea.Left > 0 ?
dm.MonitorArea.Left : -dm.MonitorArea.Left) < dInfo.ScreenWidth;
topLeft = left && top;
topRight = right && top;
bottomLeft = left && bottom;
bottomRight = right && bottom;
if (topLeft || topRight || bottomLeft || bottomRight || left || right)
{
if (dm.MonitorArea.Left < 0)
zeroOutX = true;
else
totalPosX += dInfo.ScreenWidth;
if (dm.MonitorArea.Top < 0)
zeroOutY = true;
else
totalPosY += dm.MonitorArea.Top;
}
dInfo = dm;
}
Display display = new Display(dm.DeviceName, dm.ScreenWidth,
dm.ScreenHeight, zeroOutX ? 0 : totalPosX,
zeroOutY ? 0 : totalPosY, Controls, count);
Displays.Add(display);
count++;
}
I cant find much help on this matter and have tried numerous ways to do this its a c# windows form and I am new-ish to programming my knowledge is limited in this any help will be appreciated thanks in advance.
According to the EnumDisplaySettings:
The EnumDisplaySettings function sets values for the following five
DEVMODE members:
dmBitsPerPel
dmPelsWidth
dmPelsHeight
dmDisplayFlags
dmDisplayFrequency
(Excluding dmPosition), You should try to use EnumDisplaySettingsEx, and specify DM_POSITION to get the correct dmPosition value.
thanks for all that helped I found a solution to my problem here it is if anyone need to know.
var or = SystemInformation.VirtualScreen;
foreach (DisplayInfo dm in dic)
{
int x = or.Left > 0 ? or.Left : -or.Left;
int y = or.Top > 0 ? or.Top : -or.Top;
if (dm.isPrimary)
{
Rect rect = new Rect();
rect.Left = x;
rect.Top = y;
rect.Right = rect.Left + dm.ScreenWidth;
rect.Bottom = rect.Top + dm.ScreenHeight;
dm.MonitorArea = rect;
}
else
{
Rect rect = new Rect();
rect.Left = x + dm.MonitorArea.Left;
rect.Top = y + dm.MonitorArea.Top;
rect.Right = rect.Left + dm.ScreenWidth;
rect.Bottom = rect.Top + dm.ScreenHeight;
dm.MonitorArea = rect;
}

Name/Term of an unknown feature

I would like to implement a feature in my application, the problem is that I don't know where to search because I don't know the name/term of this feature.
In some applications, when you move the Form near a border/corner of the screen, the application automatically self adhering to that border until you drag the form far the border to un-adherit.
I don't have any application example to show using this feature, sorry about that.
Someone could explain me which is the name/term of this feature, and where I can find a source to examine the techniques used to implement this feature (in WinForms)?.
That feature is sometimes called 'snapping', 'sticky' or 'magnetic' windows, like used in WinAmp. An example implementation can be found at CodeProject: A .NET Snap To Screen Form.
The C# version comes down to this:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
public partial class Form1 : Form
{
private const int SnapOffset = 35;
private const int WM_WINDOWPOSCHANGING = 70;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
SnapToDesktopBorder(this, m.LParam, 0);
}
base.WndProc(ref m);
}
private void SnapToDesktopBorder(Form clientForm, IntPtr intPtr, int widthAdjustment)
{
var newPosition = new WINDOWPOS();
newPosition = (WINDOWPOS)System.Runtime.InteropServices.Marshal.PtrToStructure(intPtr, typeof(WINDOWPOS));
if (newPosition.y == 0 || newPosition.x == 0)
{
return;
// Nothing to do!
}
// Adjust the client size for borders and caption bar
Rectangle ClientRect = clientForm.RectangleToScreen(clientForm.ClientRectangle);
ClientRect.Width += (SystemInformation.FrameBorderSize.Width * 2) - widthAdjustment;
ClientRect.Height += (SystemInformation.FrameBorderSize.Height * 2) + SystemInformation.CaptionHeight;
// Now get the screen working area (without taskbar)
Rectangle WorkingRect = Screen.FromControl(clientForm).WorkingArea;
// Left border
if (newPosition.x >= WorkingRect.X - SnapOffset && newPosition.x <= WorkingRect.X + SnapOffset)
{
newPosition.x = WorkingRect.X;
}
// Get screen bounds and taskbar height
// (when taskbar is horizontal)
Rectangle ScreenRect = Screen.FromControl(clientForm).Bounds;
int TaskbarHeight = ScreenRect.Height - WorkingRect.Height;
// Top border (check if taskbar is on top
// or bottom via WorkingRect.Y)
if (newPosition.y >= -SnapOffset && (WorkingRect.Y > 0 && newPosition.y <= (TaskbarHeight + SnapOffset)) || (WorkingRect.Y <= 0 && newPosition.y <= (SnapOffset)))
{
if (TaskbarHeight > 0)
{
newPosition.y = WorkingRect.Y;
// Horizontal Taskbar
}
else
{
newPosition.y = 0;
// Vertical Taskbar
}
}
// Right border
if (newPosition.x + ClientRect.Width <= WorkingRect.Right + SnapOffset && newPosition.x + ClientRect.Width >= WorkingRect.Right - SnapOffset)
{
newPosition.x = WorkingRect.Right - (ClientRect.Width + SystemInformation.FrameBorderSize.Width);
}
// Bottom border
if (newPosition.y + ClientRect.Height <= WorkingRect.Bottom + SnapOffset && newPosition.y + ClientRect.Height >= WorkingRect.Bottom - SnapOffset)
{
newPosition.y = WorkingRect.Bottom - (ClientRect.Height + SystemInformation.FrameBorderSize.Height);
}
// Marshal it back
System.Runtime.InteropServices.Marshal.StructureToPtr(newPosition, intPtr, true);
}
}
But the code seems to be a bit bloated, I think it can be greatly simplified. It also only works on desktop borders, not other windows.
See also Anyone familiar with a good “sticky windows” library for Winforms?, both answers linking to other CodeProject solutions: SnapFormExtender - a magnet for your MDI child forms (2004) and Sticky Windows - How to make your (top-level) forms to stick one to the other or to the screen, also from 2004.

Override resize behavior of winform window

I have a winform window. When I change the size of the screen, the screen immediately increases or decreases.
I would prefer the Resize behavior of the window will be like Split Container, as long as I drag the mouse I see only line that marks what will be the window size, and only in leaving the Resize operation will be made.
I saw several examples that show that by hiding the frame of the window, and then by clicking on the window itself paint frame.
I want that by clicking on the frame of the window(I don't want to hide the frame) and not on the window.
Is there any way to do this? (May override the behavior of the Resize in any way).
I'm pretty sure that you can't find any solution on the Internet. However I've tried a demo for this and it works pretty well.
In winforms and many other UI technologies, you can't render something outside the window itself. To get the effect we want, we have to render some indicative border outside or inside the window depending on how user resizes it. So looks like we're stuck?
BUT there is a kind of technique to do that (I call it layer technique). We need a transparent, non-focused layer to render the indicative border. This layer will have its Size and Location synchronized with the Size and Location (with just a little offset) of the main window. The layer will also be invisible by default and only shows when user resizes and hides when ending resizing.
That's pretty OK with the technique I mentioned. However how to prevent/discard the default resizing when user resizes the window? It's luckily that Win32 supports 2 messages for this to be done easily:
WM_RESIZING : is sent to the window when user starts and keeps resizing. The LParam holds the RECT structure of the current window when resizing. We read this info to render the indicative border correctly. Then we need to modify this RECT to the current Bounds of the window to discard the default resizing effect (the size and location are changed immediately).
WM_EXITSIZEMOVE : is sent to the window when the resizing or moving ends. We need to catch this message to assign the Size and Location of the window based on the Size and Location of the transparent layer and of course hide the layer then.
Now the problem is totally solvable. Here is the demo code I've made. Note that there is a very nasty unsolvable and incomprehensible bug here, it happens when you resize the Top-Left corner, the Size is updated correctly after releasing mouse but the Location is set with an offset. I've debugging but no luck. At some point the Top and Left jumps to unexpected values for no clear reason. However, resizing by all the sides (left, top, right, bottom) and other corners is OK. In fact, resizing by the Top-Left corner is hardly done by user so this solution is acceptable, I think.
//Must add using System.Runtime.InteropServices;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//Sizing border initialization
SizingBorderWidth = 3;
SizingBorderStyle = DashStyle.Custom;
SizingBorderColor = Color.Orange;
//layer initialization
layer.Owner = this;//especially this one.
layer.Width = Width + SizingBorderWidth * 2;
layer.Height = Height + SizingBorderWidth * 2;
//Paint the border when sizing
layer.Paint += (s, e) => {
using (Pen p = new Pen(SizingBorderColor) { Width = SizingBorderWidth }) {
if (Use3DSizingBorder) {
ControlPaint.DrawBorder3D(e.Graphics, sizingRect.Left, sizingRect.Top, sizingRect.Width, sizingRect.Height, Border3DStyle.Bump, Border3DSide.All);
}
else {
p.DashStyle = SizingBorderStyle;
p.LineJoin = LineJoin.Round;
if(p.DashStyle == DashStyle.Custom)
p.DashPattern = new float[] { 8f, 1f, 1f, 1f };//length of each dash from right to left
e.Graphics.DrawRectangle(p, sizingRect);
}
}
};
//Bind the Location of the main form and the layer form together
LocationChanged += (s, e) => {
Point p = Location;
p.Offset(-SizingBorderWidth, -SizingBorderWidth);
layer.Location = p;
};
//Set the intial Location of layer
Load += (s, e) =>{
Point p = Location;
p.Offset(-SizingBorderWidth, -SizingBorderWidth);
layer.Location = p;
};
}
//Set this to true to use 3D indicative/preview border
public bool Use3DSizingBorder { get; set; }
//Change the indicative/preview border thickness
public int SizingBorderWidth { get; set; }
//Change the indicative/preview border style
public DashStyle SizingBorderStyle { get; set; }
//Change the indicative/preview border color
public Color SizingBorderColor { get; set; }
//hold the current sizing Rectangle
Rectangle sizingRect;
bool startSizing;
bool suppressSizing;
//This is a Win32 RECT struct (don't use Rectangle)
public struct RECT
{
public int left, top, right, bottom;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x214&&!suppressSizing)//WM_SIZING = 0x214
{
RECT rect = (RECT) m.GetLParam(typeof(RECT));
int w = rect.right - rect.left;
int h = rect.bottom - rect.top;
sizingRect = new Rectangle() {X = SizingBorderWidth/2, Y = SizingBorderWidth/2,
Width = w, Height = h};
layer.Left = rect.left-SizingBorderWidth;
layer.Top = rect.top-SizingBorderWidth;
layer.Width = w+2*SizingBorderWidth;
layer.Height = h+2*SizingBorderWidth;
if (!startSizing)
{
layer.Show();
startSizing = true;
}
layer.Invalidate();
//Keep the current position and size fixed
rect.right = Right;
rect.bottom = Bottom;
rect.top = Top;
rect.left = Left;
//---------------------------
Marshal.StructureToPtr(rect, m.LParam, true);
}
if (m.Msg == 0x232)//WM_EXITSIZEMOVE = 0x232
{
layer.Visible = false;
BeginInvoke((Action)(() => {
suppressSizing = true;
Left = layer.Left + SizingBorderWidth;
Top = layer.Top + SizingBorderWidth;
Width = layer.Width - 2 * SizingBorderWidth;
Height = layer.Height - SizingBorderWidth * 2;
suppressSizing = false;
}));
startSizing = false;
}
base.WndProc(ref m);
}
//Here is the layer I mentioned before.
NoActivationForm layer = new NoActivationForm();
}
public class NoActivationForm : Form {
public NoActivationForm() {
//The following initialization is very important
TransparencyKey = BackColor;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
//----------------------------------------------
}
protected override bool ShowWithoutActivation {
get { return true; }
}
}
Some screen shots:
EDIT: (This edit was suggested by Hodaya Shalom, the OP (weird :)
I found a solution to the left corner problem :
before the BeginInvoke I save the variables and in the invoke I put the local variable:
int _top = layer.Top + SizingBorderWidth;
int _left = layer.Left + SizingBorderWidth;
int _width = layer.Width - 2 * SizingBorderWidth;
int _height = layer.Height - SizingBorderWidth * 2;
BeginInvoke((Action)(() => {
suppressSizing = true;
Left = _left;
Top = _top;
Width =_width;
Height =_height;
suppressSizing = false;
}));

How do I get the taskbar's position and size?

I want to know how to get the rectangle (bottom, top, left, and right) that the taskbar occupies. How do I go about doing this in C#?
private enum TaskBarLocation { TOP, BOTTOM, LEFT, RIGHT}
private TaskBarLocation GetTaskBarLocation()
{
TaskBarLocation taskBarLocation = TaskBarLocation.BOTTOM;
bool taskBarOnTopOrBottom = (Screen.PrimaryScreen.WorkingArea.Width == Screen.PrimaryScreen.Bounds.Width);
if (taskBarOnTopOrBottom)
{
if (Screen.PrimaryScreen.WorkingArea.Top > 0) taskBarLocation = TaskBarLocation.TOP;
}
else
{
if (Screen.PrimaryScreen.WorkingArea.Left > 0)
{
taskBarLocation = TaskBarLocation.LEFT;
}
else
{
taskBarLocation = TaskBarLocation.RIGHT;
}
}
return taskBarLocation;
}
It's actually way more complicated than is shown above. For one thing, the task bar doesn't have to be on the primary screen, it can be dragged to any screen. For another, in theory there could be something docked on each edge of each given screen. The code above incorrectly assumes that finding something docked to one edge excludes all other edges.
The only way the location of the task bar could be definitively derived from bounds vs workingarea, would be if only one edge out of all screens had something docked to it.
The following function returns an array of Rectangles, each representing a docked task bar, and writes the count to its byref parameter. If that count is 1, element 0 of the returned array is the Rectangle occupied by the task bar. If greater than 1, time to guess?
Public Function FindDockedTaskBars(ByRef DockedRectCounter As Integer) As Rectangle()
Dim TmpScrn As Screen = Nothing
Dim LeftDockedWidth As Integer = 0
Dim TopDockedHeight As Integer = 0
Dim RightDockedWidth As Integer = 0
Dim BottomDockedHeight As Integer = 0
Dim DockedRects(Screen.AllScreens.Count * 4) As Rectangle
DockedRectCounter = 0
For Each TmpScrn In Screen.AllScreens
If Not TmpScrn.Bounds.Equals(TmpScrn.WorkingArea) Then
LeftDockedWidth = Math.Abs(Math.Abs(TmpScrn.Bounds.Left) - Math.Abs(TmpScrn.WorkingArea.Left))
TopDockedHeight = Math.Abs(Math.Abs(TmpScrn.Bounds.Top) - Math.Abs(TmpScrn.WorkingArea.Top))
RightDockedWidth = (TmpScrn.Bounds.Width - LeftDockedWidth) - TmpScrn.WorkingArea.Width
BottomDockedHeight = (TmpScrn.Bounds.Height - TopDockedHeight) - TmpScrn.WorkingArea.Height
If LeftDockedWidth > 0 Then
DockedRects(DockedRectCounter).X = TmpScrn.Bounds.Left
DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top
DockedRects(DockedRectCounter).Width = LeftDockedWidth
DockedRects(DockedRectCounter).Height = TmpScrn.Bounds.Height
DockedRectCounter += 1
End If
If RightDockedWidth > 0 Then
DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Right
DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top
DockedRects(DockedRectCounter).Width = RightDockedWidth
DockedRects(DockedRectCounter).Height = TmpScrn.Bounds.Height
DockedRectCounter += 1
End If
If TopDockedHeight > 0 Then
DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Left
DockedRects(DockedRectCounter).Y = TmpScrn.Bounds.Top
DockedRects(DockedRectCounter).Width = TmpScrn.WorkingArea.Width
DockedRects(DockedRectCounter).Height = TopDockedHeight
DockedRectCounter += 1
End If
If BottomDockedHeight > 0 Then
DockedRects(DockedRectCounter).X = TmpScrn.WorkingArea.Left
DockedRects(DockedRectCounter).Y = TmpScrn.WorkingArea.Bottom
DockedRects(DockedRectCounter).Width = TmpScrn.WorkingArea.Width
DockedRects(DockedRectCounter).Height = BottomDockedHeight
DockedRectCounter += 1
End If
End If
Next
Return DockedRects
End Function
Or for those of you who prefer C#... (Note: this ported code is untested)
using System.Drawing;
using System.Windows.Forms;
public Rectangle[] FindDockedTaskBars(ref int DockedRectCounter)
{
int LeftDockedWidth = 0;
int TopDockedHeight = 0;
int RightDockedWidth = 0;
int BottomDockedHeight = 0;
Rectangle[] DockedRects = new Rectangle[Screen.AllScreens.Count() * 4];
DockedRectCounter = 0;
foreach (Screen TmpScrn in Screen.AllScreens)
{
if (!TmpScrn.Bounds.Equals(TmpScrn.WorkingArea))
{
LeftDockedWidth = Math.Abs(Math.Abs(TmpScrn.Bounds.Left) - Math.Abs(TmpScrn.WorkingArea.Left));
TopDockedHeight = Math.Abs(Math.Abs(TmpScrn.Bounds.Top) - Math.Abs(TmpScrn.WorkingArea.Top));
RightDockedWidth = (TmpScrn.Bounds.Width - LeftDockedWidth) - TmpScrn.WorkingArea.Width;
BottomDockedHeight = (TmpScrn.Bounds.Height - TopDockedHeight) - TmpScrn.WorkingArea.Height;
if (LeftDockedWidth > 0)
{
DockedRects[DockedRectCounter].X = TmpScrn.Bounds.Left;
DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top;
DockedRects[DockedRectCounter].Width = LeftDockedWidth;
DockedRects[DockedRectCounter].Height = TmpScrn.Bounds.Height;
DockedRectCounter += 1;
}
if (RightDockedWidth > 0)
{
DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Right;
DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top;
DockedRects[DockedRectCounter].Width = RightDockedWidth;
DockedRects[DockedRectCounter].Height = TmpScrn.Bounds.Height;
DockedRectCounter += 1;
}
if (TopDockedHeight > 0)
{
DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Left;
DockedRects[DockedRectCounter].Y = TmpScrn.Bounds.Top;
DockedRects[DockedRectCounter].Width = TmpScrn.WorkingArea.Width;
DockedRects[DockedRectCounter].Height = TopDockedHeight;
DockedRectCounter += 1;
}
if (BottomDockedHeight > 0)
{
DockedRects[DockedRectCounter].X = TmpScrn.WorkingArea.Left;
DockedRects[DockedRectCounter].Y = TmpScrn.WorkingArea.Bottom;
DockedRects[DockedRectCounter].Width = TmpScrn.WorkingArea.Width;
DockedRects[DockedRectCounter].Height = BottomDockedHeight;
DockedRectCounter += 1;
}
}
}
return DockedRects;
}
Based on David's answer, here is a better implementation that uses P/Invoke to correctly determine the placement and size of the taskbar. The only limitation I know of so far is that it does not return the correct bounds when multiple monitors are set to display in extended mode.
The code with all subsequent updates is available as a gist at https://git.io/v9bCx.
/******************************************************************************
* Name: Taskbar.cs
* Description: Class to get the taskbar's position, size and other properties.
* Author: Franz Alex Gaisie-Essilfie
* based on code from https://winsharp93.wordpress.com/2009/06/29/find-out-size-and-position-of-the-taskbar/
*
* Change Log:
* Date | Description
* -------------|--------------------------------------------------------------
* 2017-05-16 | Initial design
*/
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace System.Windows.Forms
{
public enum TaskbarPosition
{
Unknown = -1,
Left,
Top,
Right,
Bottom,
}
public static class Taskbar
{
private enum ABS
{
AutoHide = 0x01,
AlwaysOnTop = 0x02,
}
////private enum ABE : uint
private enum AppBarEdge : uint
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
////private enum ABM : uint
private enum AppBarMessage : uint
{
New = 0x00000000,
Remove = 0x00000001,
QueryPos = 0x00000002,
SetPos = 0x00000003,
GetState = 0x00000004,
GetTaskbarPos = 0x00000005,
Activate = 0x00000006,
GetAutoHideBar = 0x00000007,
SetAutoHideBar = 0x00000008,
WindowPosChanged = 0x00000009,
SetState = 0x0000000A,
}
private const string ClassName = "Shell_TrayWnd";
private static APPBARDATA _appBarData;
/// <summary>Static initializer of the <see cref="Taskbar" /> class.</summary>
static Taskbar()
{
_appBarData = new APPBARDATA
{
cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA)),
hWnd = FindWindow(Taskbar.ClassName, null)
};
}
/// <summary>
/// Gets a value indicating whether the taskbar is always on top of other windows.
/// </summary>
/// <value><c>true</c> if the taskbar is always on top of other windows; otherwise, <c>false</c>.</value>
/// <remarks>This property always returns <c>false</c> on Windows 7 and newer.</remarks>
public static bool AlwaysOnTop
{
get
{
int state = SHAppBarMessage(AppBarMessage.GetState, ref _appBarData).ToInt32();
return ((ABS)state).HasFlag(ABS.AlwaysOnTop);
}
}
/// <summary>
/// Gets a value indicating whether the taskbar is automatically hidden when inactive.
/// </summary>
/// <value><c>true</c> if the taskbar is set to auto-hide is enabled; otherwise, <c>false</c>.</value>
public static bool AutoHide
{
get
{
int state = SHAppBarMessage(AppBarMessage.GetState, ref _appBarData).ToInt32();
return ((ABS)state).HasFlag(ABS.AutoHide);
}
}
/// <summary>Gets the current display bounds of the taskbar.</summary>
public static Rectangle CurrentBounds
{
get
{
var rect = new RECT();
if (GetWindowRect(Handle, ref rect))
return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
return Rectangle.Empty;
}
}
/// <summary>Gets the display bounds when the taskbar is fully visible.</summary>
public static Rectangle DisplayBounds
{
get
{
if (RefreshBoundsAndPosition())
return Rectangle.FromLTRB(_appBarData.rect.Left,
_appBarData.rect.Top,
_appBarData.rect.Right,
_appBarData.rect.Bottom);
return CurrentBounds;
}
}
/// <summary>Gets the taskbar's window handle.</summary>
public static IntPtr Handle
{
get { return _appBarData.hWnd; }
}
/// <summary>Gets the taskbar's position on the screen.</summary>
public static TaskbarPosition Position
{
get
{
if (RefreshBoundsAndPosition())
return (TaskbarPosition)_appBarData.uEdge;
return TaskbarPosition.Unknown;
}
}
/// <summary>Hides the taskbar.</summary>
public static void Hide()
{
const int SW_HIDE = 0;
ShowWindow(Handle, SW_HIDE);
}
/// <summary>Shows the taskbar.</summary>
public static void Show()
{
const int SW_SHOW = 1;
ShowWindow(Handle, SW_SHOW);
}
private static bool RefreshBoundsAndPosition()
{
//! SHAppBarMessage returns IntPtr.Zero **if it fails**
return SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref _appBarData) != IntPtr.Zero;
}
#region DllImports
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[DllImport("shell32.dll", SetLastError = true)]
private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, [In] ref APPBARDATA pData);
[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hwnd, int command);
#endregion DllImports
[StructLayout(LayoutKind.Sequential)]
private struct APPBARDATA
{
public uint cbSize;
public IntPtr hWnd;
public uint uCallbackMessage;
public AppBarEdge uEdge;
public RECT rect;
public int lParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
}
private enum TaskBarLocation { TOP, BOTTOM, LEFT, RIGHT }
private TaskBarLocation GetTaskBarLocation()
{
//System.Windows.SystemParameters....
if (SystemParameters.WorkArea.Left > 0)
return TaskBarLocation.LEFT;
if (SystemParameters.WorkArea.Top > 0)
return TaskBarLocation.TOP;
if (SystemParameters.WorkArea.Left == 0
&& SystemParameters.WorkArea.Width < SystemParameters.PrimaryScreenWidth)
return TaskBarLocation.RIGHT;
return TaskBarLocation.BOTTOM;
}
This is the answer from Mark McGinty in C#.
This code brings back all of the task bars as a list of rectanges:
0 rectangles means the taskbar is hidden;
1 rectangle is the position of the taskbar;
2+ is very rare, it means that we have multiple monitors, and we are not using Extend these displays to create a single virtual desktop.
Works in every situation
It works well with:
Windows 7 (will almost certainly work on Windows 8.1 and Windows 10).
All combinations of settings.
C# Code
public static List<Rectangle> FindDockedTaskBars()
{
List<Rectangle> dockedRects = new List<Rectangle>();
foreach (var tmpScrn in Screen.AllScreens)
{
if (!tmpScrn.Bounds.Equals(tmpScrn.WorkingArea))
{
Rectangle rect = new Rectangle();
var leftDockedWidth = Math.Abs((Math.Abs(tmpScrn.Bounds.Left) - Math.Abs(tmpScrn.WorkingArea.Left)));
var topDockedHeight = Math.Abs((Math.Abs(tmpScrn.Bounds.Top) - Math.Abs(tmpScrn.WorkingArea.Top)));
var rightDockedWidth = ((tmpScrn.Bounds.Width - leftDockedWidth) - tmpScrn.WorkingArea.Width);
var bottomDockedHeight = ((tmpScrn.Bounds.Height - topDockedHeight) - tmpScrn.WorkingArea.Height);
if ((leftDockedWidth > 0))
{
rect.X = tmpScrn.Bounds.Left;
rect.Y = tmpScrn.Bounds.Top;
rect.Width = leftDockedWidth;
rect.Height = tmpScrn.Bounds.Height;
}
else if ((rightDockedWidth > 0))
{
rect.X = tmpScrn.WorkingArea.Right;
rect.Y = tmpScrn.Bounds.Top;
rect.Width = rightDockedWidth;
rect.Height = tmpScrn.Bounds.Height;
}
else if ((topDockedHeight > 0))
{
rect.X = tmpScrn.WorkingArea.Left;
rect.Y = tmpScrn.Bounds.Top;
rect.Width = tmpScrn.WorkingArea.Width;
rect.Height = topDockedHeight;
}
else if ((bottomDockedHeight > 0))
{
rect.X = tmpScrn.WorkingArea.Left;
rect.Y = tmpScrn.WorkingArea.Bottom;
rect.Width = tmpScrn.WorkingArea.Width;
rect.Height = bottomDockedHeight;
}
else
{
// Nothing found!
}
dockedRects.Add(rect);
}
}
if (dockedRects.Count == 0)
{
// Taskbar is set to "Auto-Hide".
}
return dockedRects;
}
This is a simple example using winforms with wpf and multi screen support:
Screen sc = Screen.FromHandle(new WindowInteropHelper(this).Handle);
if (sc.WorkingArea.Top > 0)
{
// TASKBAR TOP
}
else if (sc.WorkingArea.Left != sc.Bounds.X)
{
// TASKBAR LEFT
}
else if ((sc.Bounds.Height - sc.WorkingArea.Height) > 0)
{
// TASKBAR BOTTOM
}
else if (sc.WorkingArea.Right != 0)
{
// TASKBAR RIGHT
}
else
{
// TASKBAR NOT FOUND
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT { public Int32 left; public Int32 top; public Int32 right; public Int32 bottom; }
[StructLayout(LayoutKind.Sequential)]
public struct APPBARDATA { public UInt32 cbSize; public IntPtr hWnd; public UInt32 uCallbackMessage; public UInt32 uEdge; public RECT rc; public IntPtr lParam; }
[DllImport("shell32.dll")]
public static extern IntPtr SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA pData);
private void Form1_Load(object sender, EventArgs e)
{
APPBARDATA msgData = new APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
// get taskbar position
SHAppBarMessage((UInt32)0x00000005, ref msgData);
RECT taskRect = msgData.rc;
Console.WriteLine("top:" + taskRect.top + "; left:" + taskRect.left + "; bottom:" + taskRect.bottom + "; right:" + taskRect.right);
Console.WriteLine("width:" + (taskRect.right - taskRect.left) + "; height:" + (taskRect.bottom - taskRect.top));
}
Output: top:1040; left:0; bottom:1080; right:1920 width:1920;
height:40
This is how to get the Taskbar's Height (using WPF)
int PSBH = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height
int TaskBarHeight = PSBH - System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
If you want to account for dpi
int PSH = SystemParameters.PrimaryScreenHeight;
int PSBH = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
double ratio = PSH / PSBH;
int TaskBarHeight = PSBH - System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
TaskBarHeight *= ratio;
I hate to say this, but one of the best and most consistent methods for setting your window size when using a WindowStyle of none is to create a temporary window that is created, maximized, Width and Height recorded, and then destroyed.
private (double height, double width) GetVirtualWindowSize()
{
Window virtualWindow = new Window();
virtualWindow.Show();
virtualWindow.WindowState = WindowState.Maximized;
double returnHeight = virtualWindow.Height;
double returnWidth = virtualWindow.Width;
virtualWindow.Close();
return (returnHeight, returnWidth);
}
You can then set the MaxHeight and MaxWidth properties of your window using the values returned within the tuple. This "sacrificial" window will use the standard window frame, and so Windows knows how to properly maximize it, thus returning accurate values every time.
You can do this at startup so that you only have to deal with the window creation once, or you can recheck the dimensions on every maximize.
I know it isn't pretty, and there is a brief flash. But it always works for me, no matter where the taskbar is, no matter the scaling, and no matter the changes that have taken place since the application started.
This will work on Windows and macOS. It will also work with multiple monitors. As is, it is for electron applications, but you can easily understand what's going on.
type TaskBarPos int
const (
UNKNOWN TaskBarPos = -1
LEFT TaskBarPos = 0
RIGHT TaskBarPos = 1
TOP TaskBarPos = 2
BOTTOM TaskBarPos = 3
)
type Rect struct {
top int
bottom int
width int
height int
left int
right int
}
func (r Rect) centerX() int {
return r.left + ((r.right - r.left) / 2)
}
func (r Rect) centerY() int {
return r.top + ((r.bottom - r.top) / 2)
}
func taskbar(tray *js.Object) TaskBarPos {
// Step 1 - Get relevant display
display := screen.Call("getDisplayNearestPoint", screen.Call("getCursorScreenPoint")) // Replace with primary monitor or a secondary monitor. This line as is grabs the monitor that the mouse cursor is on.
// Step 2 - Determine taskbar bounds relative to the display
bounds := display.Get("bounds")
workArea := display.Get("workArea")
var tb *Rect
d := Rect{
top: bounds.Get("y").Int(),
bottom: bounds.Get("y").Int() + bounds.Get("height").Int(),
width: bounds.Get("width").Int(),
height: bounds.Get("height").Int(),
left: bounds.Get("x").Int(),
right: bounds.Get("x").Int() + bounds.Get("width").Int(),
}
wa := Rect{
top: workArea.Get("y").Int(),
bottom: workArea.Get("y").Int() + workArea.Get("height").Int(),
width: workArea.Get("width").Int(),
height: workArea.Get("height").Int(),
left: workArea.Get("x").Int(),
right: workArea.Get("x").Int() + workArea.Get("width").Int(),
}
if tray != nil {
tBounds := tray.Call("getBounds")
tb = &Rect{
top: tBounds.Get("y").Int(),
bottom: tBounds.Get("y").Int() + tBounds.Get("height").Int(),
width: tBounds.Get("width").Int(),
height: tBounds.Get("height").Int(),
left: tBounds.Get("x").Int(),
right: tBounds.Get("x").Int() + tBounds.Get("width").Int(),
}
}
// Step 3 - Determine Position of Taskbar
if wa.top > d.top {
return TOP
} else if wa.bottom < d.bottom {
return BOTTOM
} else if wa.left > d.left {
return LEFT
} else if wa.right < d.right {
return RIGHT
}
if tb == nil {
return UNKNOWN
}
// Check which corner tray is closest to
if ((*tb).top - d.top) < (d.bottom - (*tb).bottom) {
return TOP
}
if ((*tb).left - d.left) < (d.right - (*tb).right) {
return LEFT
}
if d.bottom-(*tb).centerY() < d.right-(*tb).centerX() {
return BOTTOM
}
return RIGHT
}

MonthCalendar control selection range with EnableVisualStyles?

I'm using the MonthCalendar control and want to programmatically select a date range. When I do so the control doesn't paint properly if Application.EnableVisualStyles() has been called. This is a known issue according to MSDN.
Using the MonthCalendar with visual
styles enabled will cause a selection
range for the MonthCalendar control to
not paint correctly
(from: http://msdn.microsoft.com/en-us/library/system.windows.forms.monthcalendar.aspx)
Is there really no fix for this other than not calling EnableVisualStyles? This seems to make that particular control entirely useless for a range of applications and a rather glaring oversight from my perspective.
While looking for a solution to the same problem, I first encountered this question here, but later I discovered a blog entry by Nicke Andersson. which I found very helpful.
Here is what I made of Nicke's example:
public class MonthCalendarEx : System.Windows.Forms.MonthCalendar
{
private int _offsetX;
private int _offsetY;
private int _dayBoxWidth;
private int _dayBoxHeight;
private bool _repaintSelectedDays = false;
public MonthCalendarEx() : base()
{
OnSizeChanged(null, null);
this.SizeChanged += OnSizeChanged;
this.DateChanged += OnSelectionChanged;
this.DateSelected += OnSelectionChanged;
}
protected static int WM_PAINT = 0x000F;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics graphics = Graphics.FromHwnd(this.Handle);
PaintEventArgs pe = new PaintEventArgs(
graphics, new Rectangle(0, 0, this.Width, this.Height));
OnPaint(pe);
}
}
private void OnSelectionChanged(object sender, EventArgs e)
{
_repaintSelectedDays = true;
}
private void OnSizeChanged(object sender, EventArgs e)
{
_offsetX = 0;
_offsetY = 0;
// determine Y offset of days area
while (
HitTest(Width / 2, _offsetY).HitArea != HitArea.PrevMonthDate &&
HitTest(Width / 2, _offsetY).HitArea != HitArea.Date)
{
_offsetY++;
}
// determine X offset of days area
while (HitTest(_offsetX, Height / 2).HitArea != HitArea.Date)
{
_offsetX++;
}
// determine width of a single day box
_dayBoxWidth = 0;
DateTime dt1 = HitTest(Width / 2, _offsetY).Time;
while (HitTest(Width / 2, _offsetY + _dayBoxHeight).Time == dt1)
{
_dayBoxHeight++;
}
// determine height of a single day box
_dayBoxWidth = 0;
DateTime dt2 = HitTest(_offsetX, Height / 2).Time;
while (HitTest(_offsetX + _dayBoxWidth, Height / 2).Time == dt2)
{
_dayBoxWidth++;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (_repaintSelectedDays)
{
Graphics graphics = e.Graphics;
SelectionRange calendarRange = GetDisplayRange(false);
Rectangle currentDayFrame = new Rectangle(
-1, -1, _dayBoxWidth, _dayBoxHeight);
DateTime current = SelectionStart;
while (current <= SelectionEnd)
{
Rectangle currentDayRectangle;
using (Brush selectionBrush = new SolidBrush(
Color.FromArgb(
255, System.Drawing.SystemColors.ActiveCaption)))
{
TimeSpan span = current.Subtract(calendarRange.Start);
int row = span.Days / 7;
int col = span.Days % 7;
currentDayRectangle = new Rectangle(
_offsetX + (col + (ShowWeekNumbers ? 1 : 0)) * _dayBoxWidth,
_offsetY + row * _dayBoxHeight,
_dayBoxWidth,
_dayBoxHeight);
graphics.FillRectangle(selectionBrush, currentDayRectangle);
}
TextRenderer.DrawText(
graphics,
current.Day.ToString(),
Font,
currentDayRectangle,
System.Drawing.SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
if (current == this.TodayDate)
{
currentDayFrame = currentDayRectangle;
}
current = current.AddDays(1);
}
if (currentDayFrame.X > 0)
{
graphics.DrawRectangle(new Pen(
new SolidBrush(Color.Red)), currentDayFrame);
}
_repaintSelectedDays = false;
}
}
}
I found a small problem in Mark Cranness's code above: On XP systems that have visual styles disabled entirely, Application.RenderWithVisualStyles is then set to False even when Application.EnableVisualStyles() is called.
So the custom paint code does not run at all in that case. To fix it, I changed the first line of the FixVisualStylesMonthCalendar constructor to
if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled &&
Environment.OSVersion.Version < new Version(6, 0))
Entire code is at the bottom of this answer.
I could not find any way to comment on the answer itself. Credits for below code go to the original author - (If he or anyone can verify this answer and update it I would be happy to remove this one)
/// <summary>
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange
/// does not paint correctly when more than one date is selected.
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly
/// in certain situations. These include the MonthCalendar control with a selection range set...
/// This class fixes that problem.
/// </summary>
/// <remarks>Author: Mark Cranness - PatronBase Limited.</remarks>
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar
{
/// <summary>
/// The width of a single cell (date) in the calendar.
/// </summary>
private int dayCellWidth;
/// <summary>
/// The height of a single cell (date) in the calendar.
/// </summary>
private int dayCellHeight;
/// <summary>
/// The calendar first day of the week actually used.
/// </summary>
private DayOfWeek calendarFirstDayOfWeek;
/// <summary>
/// Only repaint when VisualStyles enabled on Windows XP.
/// </summary>
private bool repaintSelectionRange = false;
/// <summary>
/// A MonthCalendar class that fixes SelectionRange painting problems
/// on Windows XP when Visual Styles is enabled.
/// </summary>
public FixVisualStylesMonthCalendar()
{
if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled && //Application.RenderWithVisualStyles &&
Environment.OSVersion.Version < new Version(6, 0))
{
// If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange
this.repaintSelectionRange = true;
this.OnSizeChanged(this, EventArgs.Empty);
this.SizeChanged += new EventHandler(this.OnSizeChanged);
}
}
/// <summary>
/// The WM_PAINT message is sent to make a request to paint a portion of a window.
/// </summary>
public const int WM_PAINT = 0x000F;
/// <summary>
/// Override WM_PAINT to repaint the selection range.
/// </summary>
[System.Diagnostics.DebuggerStepThroughAttribute()]
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT
&& !this.DesignMode
&& this.repaintSelectionRange)
{
// MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised
this.RepaintSelectionRange(ref m);
}
}
/// <summary>
/// Repaint the SelectionRange.
/// </summary>
private void RepaintSelectionRange(ref Message m)
{
using (Graphics graphics = this.CreateGraphics())
using (Brush backBrush
= new SolidBrush(graphics.GetNearestColor(this.BackColor)))
using (Brush selectionBrush
= new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption)))
{
Rectangle todayFrame = Rectangle.Empty;
// For each day in SelectionRange...
for (DateTime selectionDate = this.SelectionStart;
selectionDate <= this.SelectionEnd;
selectionDate = selectionDate.AddDays(1))
{
Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate);
if (selectionDayRectangle.IsEmpty) continue;
if (selectionDate.Date == this.TodayDate)
{
todayFrame = selectionDayRectangle;
}
// Paint as 'selected' a little smaller than the whole rectangle
Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2);
if (selectionDate == this.SelectionStart)
{
highlightRectangle.X += 2;
highlightRectangle.Width -= 2;
}
if (selectionDate == this.SelectionEnd)
{
highlightRectangle.Width -= 2;
}
// Paint background, selection and day-of-month text
graphics.FillRectangle(backBrush, selectionDayRectangle);
graphics.FillRectangle(selectionBrush, highlightRectangle);
TextRenderer.DrawText(
graphics,
selectionDate.Day.ToString(),
this.Font,
selectionDayRectangle,
SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
if (this.ShowTodayCircle && !todayFrame.IsEmpty)
{
// Redraw the ShowTodayCircle (square) that we painted over above
using (Pen redPen = new Pen(Color.Red))
{
todayFrame.Width--;
todayFrame.Height--;
graphics.DrawRectangle(redPen, todayFrame);
}
}
}
}
/// <summary>
/// When displayed dates changed, clear the cached month locations.
/// </summary>
private SelectionRange previousDisplayedDates = new SelectionRange();
/// <summary>
/// Gets a graphics Rectangle for the area corresponding to a single date on the calendar.
/// </summary>
private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime)
{
// Handle the leading and trailing dates from the previous and next months
SelectionRange allDisplayedDates = this.GetDisplayRange(false);
SelectionRange fullMonthDates = this.GetDisplayRange(true);
int adjust1Week;
DateTime selectionDate = selectionDateTime.Date;
if (selectionDate < allDisplayedDates.Start
|| selectionDate > allDisplayedDates.End)
{
// Selection Date is not displayed on calendar
return Rectangle.Empty;
}
else if (selectionDate < fullMonthDates.Start)
{
// Selection Date is trailing from the previous partial month
selectionDate = selectionDate.AddDays(7);
adjust1Week = -1;
}
else if (selectionDate > fullMonthDates.End)
{
// Selection Date is leading from the next partial month
selectionDate = selectionDate.AddDays(-14);
adjust1Week = +2;
}
else
{
// A mainline date
adjust1Week = 0;
}
// Discard cached month locations when calendar moves
if (this.previousDisplayedDates.Start != allDisplayedDates.Start
|| this.previousDisplayedDates.End != allDisplayedDates.End)
{
this.DiscardCachedMonthDateAreaLocations();
this.previousDisplayedDates.Start = allDisplayedDates.Start;
this.previousDisplayedDates.End = allDisplayedDates.End;
}
Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate);
if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty;
DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek;
int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek;
if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7;
int row = (selectionDate.Day - 1 + dayOfWeekAdjust) / 7;
int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7;
row += adjust1Week;
return new Rectangle(
monthDateAreaLocation.X + col * this.dayCellWidth,
monthDateAreaLocation.Y + row * this.dayCellHeight,
this.dayCellWidth,
this.dayCellHeight);
}
/// <summary>
/// Cached calendar location from the last lookup.
/// </summary>
private Point[] cachedMonthDateAreaLocation = new Point[13];
/// <summary>
/// Discard the cached month locations when calendar moves.
/// </summary>
private void DiscardCachedMonthDateAreaLocations()
{
for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty;
}
/// <summary>
/// Gets the graphics location (x,y point) of the top left of the
/// calendar date area for the month containing the specified date.
/// </summary>
private Point GetMonthDateAreaLocation(DateTime selectionDate)
{
Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month];
HitTestInfo hitInfo;
if (!monthDateAreaLocation.IsEmpty
&& (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month)
{
// Use previously cached lookup
return monthDateAreaLocation;
}
else
{
// Assume the worst (Error: empty)
monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty;
Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate);
if (monthDataAreaPoint.IsEmpty) return Point.Empty;
// Move left from the middle to find the left edge of the Date area
monthDateAreaLocation.X = monthDataAreaPoint.X--;
HitTestInfo hitInfo1, hitInfo2;
while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y))
.HitArea == HitArea.Date
&& hitInfo1.Time.Month == selectionDate.Month
|| (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo2.Time.Month == selectionDate.Month)
{
monthDateAreaLocation.X = monthDataAreaPoint.X--;
if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail
}
// Move up from the last column to find the top edge of the Date area
int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13) / 14;
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date)
{
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail
}
// Got it
this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation;
return monthDateAreaLocation;
}
}
/// <summary>
/// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case
/// our first estimate to hit the month misses.
/// (Needed? perhaps not.)
/// </summary>
private static Point[] searchSpiral = {
new Point( 0, 0),
new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1),
new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2)
};
/// <summary>
/// Gets a point somewhere inside the calendar date area of
/// the month containing the given selection date.
/// </summary>
/// <remarks>The point returned will be HitArea.Date, and match the year and
/// month of the selection date; otherwise it will be Point.Empty.</remarks>
private Point GetMonthDateAreaMiddle(DateTime selectionDate)
{
// Iterate over all displayed months, and a search spiral (needed? perhaps not)
for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++)
{
for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++)
{
foreach (Point search in searchSpiral)
{
Point monthDateAreaMiddle = new Point(
((dimX - 1) * 2 + 1) * this.Width / (2 * this.CalendarDimensions.Width)
+ this.dayCellWidth * search.X,
((dimY - 1) * 2 + 1) * this.Height / (2 * this.CalendarDimensions.Height)
+ this.dayCellHeight * search.Y);
HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle);
if (hitInfo.HitArea == HitArea.Date)
{
// Got the Date Area of the month
if (hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month)
{
// For the correct month
return monthDateAreaMiddle;
}
else
{
// Keep looking in the other months
break;
}
}
}
}
}
return Point.Empty; // Error: not found
}
/// <summary>
/// When this MonthCalendar is resized, recalculate the size of a day cell.
/// </summary>
private void OnSizeChanged(object sender, EventArgs e)
{
// Discard previous cached Month Area Location
DiscardCachedMonthDateAreaLocations();
this.dayCellWidth = this.dayCellHeight = 0;
// Without this, the repaint sometimes does not happen...
this.Invalidate();
// Determine Y offset of days area
int middle = this.Width / (2 * this.CalendarDimensions.Width);
int dateAreaTop = 0;
while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate
&& this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date)
{
dateAreaTop++;
if (dateAreaTop > this.ClientSize.Height) return; // Error: bail
}
// Determine height of a single day box
int dayCellHeight = 1;
DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time;
while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime)
{
dayCellHeight++;
}
// Determine X offset of days area
middle = this.Height / (2 * this.CalendarDimensions.Height);
int dateAreaLeft = 0;
while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date)
{
dateAreaLeft++;
if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail
}
// Determine width of a single day box
int dayCellWidth = 1;
dayCellTime = this.HitTest(dateAreaLeft, middle).Time;
while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime)
{
dayCellWidth++;
}
// Record day box size and actual first day of the month used
this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek;
this.dayCellWidth = dayCellWidth;
this.dayCellHeight = dayCellHeight;
}
}
Here is a version that does work when more than one month is displayed (CalendarDimensions != (1,1)), and fixes some other problems also:
/// <summary>
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange
/// does not paint correctly when more than one date is selected.
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly
/// in certain situations. These include the MonthCalendar control with a selection range set...
/// This class fixes that problem.
/// </summary>
/// <remarks>Author: Mark Cranness</remarks>
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar {
/// <summary>
/// The width of a single cell (date) in the calendar.
/// </summary>
private int dayCellWidth;
/// <summary>
/// The height of a single cell (date) in the calendar.
/// </summary>
private int dayCellHeight;
/// <summary>
/// The calendar first day of the week actually used.
/// </summary>
private DayOfWeek calendarFirstDayOfWeek;
/// <summary>
/// Only repaint when VisualStyles enabled on Windows XP.
/// </summary>
private bool repaintSelectionRange = false;
/// <summary>
/// A MonthCalendar class that fixes SelectionRange painting problems
/// on Windows XP when Visual Styles is enabled.
/// </summary>
public FixVisualStylesMonthCalendar() {
if (Application.RenderWithVisualStyles
&& Environment.OSVersion.Version < new Version(6, 0)) {
// If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange
this.repaintSelectionRange = true;
this.OnSizeChanged(this, EventArgs.Empty);
this.SizeChanged += new EventHandler(this.OnSizeChanged);
}
}
/// <summary>
/// The WM_PAINT message is sent to make a request to paint a portion of a window.
/// </summary>
public const int WM_PAINT = 0x000F;
/// <summary>
/// Override WM_PAINT to repaint the selection range.
/// </summary>
[System.Diagnostics.DebuggerStepThroughAttribute()]
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT
&& !this.DesignMode
&& this.repaintSelectionRange) {
// MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised
this.RepaintSelectionRange(ref m);
}
}
/// <summary>
/// Repaint the SelectionRange.
/// </summary>
private void RepaintSelectionRange(ref Message m) {
using (Graphics graphics = this.CreateGraphics())
using (Brush backBrush
= new SolidBrush(graphics.GetNearestColor(this.BackColor)))
using (Brush selectionBrush
= new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption))) {
Rectangle todayFrame = Rectangle.Empty;
// For each day in SelectionRange...
for (DateTime selectionDate = this.SelectionStart;
selectionDate <= this.SelectionEnd;
selectionDate = selectionDate.AddDays(1)) {
Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate);
if (selectionDayRectangle.IsEmpty) continue;
if (selectionDate.Date == this.TodayDate) {
todayFrame = selectionDayRectangle;
}
// Paint as 'selected' a little smaller than the whole rectangle
Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2);
if (selectionDate == this.SelectionStart) {
highlightRectangle.X += 2;
highlightRectangle.Width -= 2;
}
if (selectionDate == this.SelectionEnd) {
highlightRectangle.Width -= 2;
}
// Paint background, selection and day-of-month text
graphics.FillRectangle(backBrush, selectionDayRectangle);
graphics.FillRectangle(selectionBrush, highlightRectangle);
TextRenderer.DrawText(
graphics,
selectionDate.Day.ToString(),
this.Font,
selectionDayRectangle,
SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
if (this.ShowTodayCircle && !todayFrame.IsEmpty) {
// Redraw the ShowTodayCircle (square) that we painted over above
using (Pen redPen = new Pen(Color.Red)) {
todayFrame.Width--;
todayFrame.Height--;
graphics.DrawRectangle(redPen, todayFrame);
}
}
}
}
/// <summary>
/// When displayed dates changed, clear the cached month locations.
/// </summary>
private SelectionRange previousDisplayedDates = new SelectionRange();
/// <summary>
/// Gets a graphics Rectangle for the area corresponding to a single date on the calendar.
/// </summary>
private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime) {
// Handle the leading and trailing dates from the previous and next months
SelectionRange allDisplayedDates = this.GetDisplayRange(false);
SelectionRange fullMonthDates = this.GetDisplayRange(true);
int adjust1Week;
DateTime selectionDate = selectionDateTime.Date;
if (selectionDate < allDisplayedDates.Start
|| selectionDate > allDisplayedDates.End) {
// Selection Date is not displayed on calendar
return Rectangle.Empty;
} else if (selectionDate < fullMonthDates.Start) {
// Selection Date is trailing from the previous partial month
selectionDate = selectionDate.AddDays(7);
adjust1Week = -1;
} else if (selectionDate > fullMonthDates.End) {
// Selection Date is leading from the next partial month
selectionDate = selectionDate.AddDays(-14);
adjust1Week = +2;
} else {
// A mainline date
adjust1Week = 0;
}
// Discard cached month locations when calendar moves
if (this.previousDisplayedDates.Start != allDisplayedDates.Start
|| this.previousDisplayedDates.End != allDisplayedDates.End) {
this.DiscardCachedMonthDateAreaLocations();
this.previousDisplayedDates.Start = allDisplayedDates.Start;
this.previousDisplayedDates.End = allDisplayedDates.End;
}
Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate);
if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty;
DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek;
int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek;
if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7;
int row = (selectionDate.Day - 1 + dayOfWeekAdjust) / 7;
int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7;
row += adjust1Week;
return new Rectangle(
monthDateAreaLocation.X + col * this.dayCellWidth,
monthDateAreaLocation.Y + row * this.dayCellHeight,
this.dayCellWidth,
this.dayCellHeight);
}
/// <summary>
/// Cached calendar location from the last lookup.
/// </summary>
private Point[] cachedMonthDateAreaLocation = new Point[13];
/// <summary>
/// Discard the cached month locations when calendar moves.
/// </summary>
private void DiscardCachedMonthDateAreaLocations() {
for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty;
}
/// <summary>
/// Gets the graphics location (x,y point) of the top left of the
/// calendar date area for the month containing the specified date.
/// </summary>
private Point GetMonthDateAreaLocation(DateTime selectionDate) {
Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month];
HitTestInfo hitInfo;
if (!monthDateAreaLocation.IsEmpty
&& (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month) {
// Use previously cached lookup
return monthDateAreaLocation;
} else {
// Assume the worst (Error: empty)
monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty;
Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate);
if (monthDataAreaPoint.IsEmpty) return Point.Empty;
// Move left from the middle to find the left edge of the Date area
monthDateAreaLocation.X = monthDataAreaPoint.X--;
HitTestInfo hitInfo1, hitInfo2;
while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y))
.HitArea == HitArea.Date
&& hitInfo1.Time.Month == selectionDate.Month
|| (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo2.Time.Month == selectionDate.Month) {
monthDateAreaLocation.X = monthDataAreaPoint.X--;
if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail
}
// Move up from the last column to find the top edge of the Date area
int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13) / 14;
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date) {
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail
}
// Got it
this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation;
return monthDateAreaLocation;
}
}
/// <summary>
/// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case
/// our first estimate to hit the month misses.
/// (Needed? perhaps not.)
/// </summary>
private static Point[] searchSpiral = {
new Point( 0, 0),
new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1),
new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2)
};
/// <summary>
/// Gets a point somewhere inside the calendar date area of
/// the month containing the given selection date.
/// </summary>
/// <remarks>The point returned will be HitArea.Date, and match the year and
/// month of the selection date; otherwise it will be Point.Empty.</remarks>
private Point GetMonthDateAreaMiddle(DateTime selectionDate) {
// Iterate over all displayed months, and a search spiral (needed? perhaps not)
for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++) {
for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++) {
foreach (Point search in searchSpiral) {
Point monthDateAreaMiddle = new Point(
((dimX - 1) * 2 + 1) * this.Width / (2 * this.CalendarDimensions.Width)
+ this.dayCellWidth * search.X,
((dimY - 1) * 2 + 1) * this.Height / (2 * this.CalendarDimensions.Height)
+ this.dayCellHeight * search.Y);
HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle);
if (hitInfo.HitArea == HitArea.Date) {
// Got the Date Area of the month
if (hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month) {
// For the correct month
return monthDateAreaMiddle;
} else {
// Keep looking in the other months
break;
}
}
}
}
}
return Point.Empty; // Error: not found
}
/// <summary>
/// When this MonthCalendar is resized, recalculate the size of a day cell.
/// </summary>
private void OnSizeChanged(object sender, EventArgs e) {
// Discard previous cached Month Area Location
DiscardCachedMonthDateAreaLocations();
this.dayCellWidth = this.dayCellHeight = 0;
// Without this, the repaint sometimes does not happen...
this.Invalidate();
// Determine Y offset of days area
int middle = this.Width / (2 * this.CalendarDimensions.Width);
int dateAreaTop = 0;
while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate
&& this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date) {
dateAreaTop++;
if (dateAreaTop > this.ClientSize.Height) return; // Error: bail
}
// Determine height of a single day box
int dayCellHeight = 1;
DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time;
while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime) {
dayCellHeight++;
}
// Determine X offset of days area
middle = this.Height / (2 * this.CalendarDimensions.Height);
int dateAreaLeft = 0;
while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date) {
dateAreaLeft++;
if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail
}
// Determine width of a single day box
int dayCellWidth = 1;
dayCellTime = this.HitTest(dateAreaLeft, middle).Time;
while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime) {
dayCellWidth++;
}
// Record day box size and actual first day of the month used
this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek;
this.dayCellWidth = dayCellWidth;
this.dayCellHeight = dayCellHeight;
}
}
My testing shows that Windows 7 does not have the painting problem and I expect that neither does Vista, so this only attempts a fix for Windows XP.
you can try this code:
Dim StartDate As Date = New DateTime(2011, 9, 21)
Dim EndDate As Date = New DateTime(2011, 9, 25)
MonthCalendar1.SelectionRange = New SelectionRange(StartDate, EndDate)
for more information:
http://www.authorcode.com/how-to-select-a-range-of-dates-in-the-monthcalendar-control/
http://www.authorcode.com/how-to-enable-windows-xp-visual-styles-of-net-application/

Categories

Resources