diff --git a/src/Common/src/CompModSwitches.cs b/src/Common/src/CompModSwitches.cs index ae6bdd9c862..bf57603f201 100644 --- a/src/Common/src/CompModSwitches.cs +++ b/src/Common/src/CompModSwitches.cs @@ -7,14 +7,9 @@ namespace System.ComponentModel { using System.Diagnostics; - - /// - // Shared between dlls internal static class CompModSwitches { - #if WINDOWS_FORMS_SWITCHES - private static TraceSwitch activeX; private static TraceSwitch flowLayout; private static TraceSwitch dataCursor; @@ -44,7 +39,10 @@ internal static class CompModSwitches { private static TraceSwitch setBounds; private static BooleanSwitch lifetimeTracing; - + + private static TraceSwitch s_handleLeak; + private static BooleanSwitch s_commonDesignerServices; + public static TraceSwitch ActiveX { get { if (activeX == null) { @@ -286,8 +284,7 @@ public static TraceSwitch RichLayout { } return richLayout; } - } - + } public static TraceSwitch SetBounds { get { @@ -296,20 +293,15 @@ public static TraceSwitch SetBounds { } return setBounds; } - } - + } #endif - - - private static TraceSwitch handleLeak; - public static TraceSwitch HandleLeak { get { - if (handleLeak == null) { - handleLeak = new TraceSwitch("HANDLELEAK", "HandleCollector: Track Win32 Handle Leaks"); + if (s_handleLeak == null) { + s_handleLeak = new TraceSwitch("HANDLELEAK", "HandleCollector: Track Win32 Handle Leaks"); } - return handleLeak; + return s_handleLeak; } } @@ -322,6 +314,16 @@ public static BooleanSwitch TraceCollect { return traceCollect; } } - + internal static BooleanSwitch CommonDesignerServices + { + get + { + if (s_commonDesignerServices == null) + { + s_commonDesignerServices = new BooleanSwitch("CommonDesignerServices", "Assert if any common designer service is not found."); + } + return s_commonDesignerServices; + } + } } } diff --git a/src/Common/src/NativeMethods.cs b/src/Common/src/NativeMethods.cs index 10eb0da4654..44ece3695ab 100644 --- a/src/Common/src/NativeMethods.cs +++ b/src/Common/src/NativeMethods.cs @@ -55,7 +55,8 @@ namespace System.Windows.Forms { using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.Versioning; - + using static System.Windows.Forms.NativeMethods; + /// internal static class NativeMethods { @@ -975,6 +976,12 @@ public const int public const int LOCALE_IMEASURE = 0x0000000D; // 0 = metric, 1 = US + public const int TVM_SETEXTENDEDSTYLE = TV_FIRST + 44; + public const int TVM_GETEXTENDEDSTYLE = TV_FIRST + 45; + + public const int TVS_EX_FADEINOUTEXPANDOS = 0x0040; + public const int TVS_EX_DOUBLEBUFFER = 0x0004; + public static readonly int LOCALE_USER_DEFAULT = MAKELCID(LANG_USER_DEFAULT); public static readonly int LANG_USER_DEFAULT = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); @@ -4793,7 +4800,8 @@ public class NMHEADER { public int iButton = 0; public IntPtr pItem = IntPtr.Zero; // HDITEM* } - + + [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] [StructLayout(LayoutKind.Sequential)] public class MOUSEHOOKSTRUCT { // pt was a by-value POINT structure @@ -6009,6 +6017,70 @@ public UiaRect(System.Drawing.Rectangle r) { // This value requires KB2533623 to be installed. // Windows Server 2003 and Windows XP: This value is not supported. internal const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] ref RECT rect, int cPoints); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] POINT pt, int cPoints); + + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr WindowFromPoint(int x, int y); + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, string lParam); + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [In, Out] TV_HITTESTINFO lParam); + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern short GetKeyState(int keyCode); + [DllImport(ExternDll.Gdi32, ExactSpelling = true, EntryPoint = "DeleteObject", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + private static extern bool IntDeleteObject(IntPtr hObject); + public static bool DeleteObject(IntPtr hObject) + { + System.Internal.HandleCollector.Remove(hObject, CommonHandles.GDI); + return IntDeleteObject(hObject); + } + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetUpdateRect(IntPtr hwnd, [In, Out] ref RECT rc, bool fErase); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetUpdateRgn(IntPtr hwnd, IntPtr hrgn, bool fErase); + + [DllImport(ExternDll.Gdi32, ExactSpelling = true, EntryPoint = "CreateRectRgn", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.Process)] + private static extern IntPtr IntCreateRectRgn(int x1, int y1, int x2, int y2); + public static IntPtr CreateRectRgn(int x1, int y1, int x2, int y2) + { + return System.Internal.HandleCollector.Add(IntCreateRectRgn(x1, y1, x2, y2), CommonHandles.GDI); + } + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr GetCursor(); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetCursorPos([In, Out] POINT pt); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent); } } diff --git a/src/Common/src/SafeNativeMethods.cs b/src/Common/src/SafeNativeMethods.cs index a1e3abda3eb..ca23a08463a 100644 --- a/src/Common/src/SafeNativeMethods.cs +++ b/src/Common/src/SafeNativeMethods.cs @@ -62,20 +62,20 @@ public static IntPtr CreateCompatibleBitmap(HandleRef hDC, int width, int height return System.Internal.HandleCollector.Add(IntCreateCompatibleBitmap(hDC, width, height), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool GetScrollInfo(HandleRef hWnd, int fnBar, [In, Out] NativeMethods.SCROLLINFO si); - [DllImport(ExternDll.Ole32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Ole32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool IsAccelerator(HandleRef hAccel, int cAccelEntries, [In] ref NativeMethods.MSG lpMsg, short[] lpwCmd); - [DllImport(ExternDll.Comdlg32, SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Comdlg32, SetLastError=true, CharSet=CharSet.Auto)] public static extern bool ChooseFont([In, Out] NativeMethods.CHOOSEFONT cf); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetBitmapBits(HandleRef hbmp, int cbBuffer, byte[] lpvBits); - [DllImport(ExternDll.Comdlg32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Comdlg32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int CommDlgExtendedError(); [DllImport(ExternDll.Oleaut32, ExactSpelling=true, CharSet=CharSet.Unicode)] @@ -130,33 +130,33 @@ public static extern bool Rectangle( public static extern bool PatBlt(HandleRef hdc, int left, int top, int width, int height, int rop); - [DllImport(ExternDll.Kernel32, EntryPoint="GetThreadLocale", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, EntryPoint="GetThreadLocale", CharSet=CharSet.Auto)] public static extern int GetThreadLCID(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetMessagePos(); - [DllImport(ExternDll.User32, SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, SetLastError=true, CharSet=CharSet.Auto)] public static extern int RegisterClipboardFormat(string format); - [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, CharSet=CharSet.Auto)] public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax); - [DllImport(ExternDll.Comdlg32, SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Comdlg32, SetLastError=true, CharSet=CharSet.Auto)] public static extern bool ChooseColor([In, Out] NativeMethods.CHOOSECOLOR cc); - [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, CharSet=CharSet.Auto)] public static extern int RegisterWindowMessage(string msg); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="DeleteObject", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="DeleteObject", CharSet=CharSet.Auto)] public static extern bool ExternalDeleteObject(HandleRef hObject); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="DeleteObject", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="DeleteObject", CharSet=CharSet.Auto)] internal static extern bool IntDeleteObject(HandleRef hObject); public static bool DeleteObject(HandleRef hObject) { @@ -168,7 +168,7 @@ public static bool DeleteObject(HandleRef hObject) { public static extern SafeNativeMethods.IFontDisp OleCreateIFontDispIndirect(NativeMethods.FONTDESC fd, ref Guid iid); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateSolidBrush", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateSolidBrush", CharSet=CharSet.Auto)] private static extern IntPtr IntCreateSolidBrush(int crColor); @@ -176,11 +176,11 @@ public static bool DeleteObject(HandleRef hObject) { public static IntPtr CreateSolidBrush(int crColor) { return System.Internal.HandleCollector.Add(IntCreateSolidBrush(crColor), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool SetWindowExtEx(HandleRef hDC, int x, int y, [In, Out] NativeMethods.SIZE size); - [DllImport(ExternDll.Kernel32, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, CharSet=CharSet.Auto)] public static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, HandleRef arguments); @@ -308,7 +308,7 @@ public static IntPtr ImageList_Read(UnsafeNativeMethods.IStream pstm) { [DllImport(ExternDll.Comctl32)] public static extern int ImageList_WriteEx(HandleRef himl, int dwFlags, UnsafeNativeMethods.IStream pstm); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags, int x, int y, HandleRef hwnd, NativeMethods.TPMPARAMS tpm); [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] @@ -341,20 +341,20 @@ public static IntPtr ImageList_Read(UnsafeNativeMethods.IStream pstm) { [DllImport(ExternDll.User32, ExactSpelling = true)] public static extern bool EnumDisplayMonitors(HandleRef hdc, NativeMethods.COMRECT rcClip, NativeMethods.MonitorEnumProc lpfnEnum, IntPtr dwData); - [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, EntryPoint = "CreateHalftonePalette", CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, EntryPoint = "CreateHalftonePalette", CharSet = CharSet.Auto)] private static extern IntPtr /*HPALETTE*/ IntCreateHalftonePalette(HandleRef hdc); public static IntPtr /*HPALETTE*/ CreateHalftonePalette(HandleRef hdc) { return System.Internal.HandleCollector.Add(IntCreateHalftonePalette(hdc), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetPaletteEntries(HandleRef hpal, int iStartIndex, int nEntries, int[] lppe); [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)] public static extern int GetTextMetricsW(HandleRef hDC, ref NativeMethods.TEXTMETRIC lptm); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateDIBSection", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateDIBSection", CharSet=CharSet.Auto)] private static extern IntPtr IntCreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, byte[] ppvBits, IntPtr hSection, int dwOffset); @@ -363,7 +363,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, return System.Internal.HandleCollector.Add(IntCreateDIBSection(hdc, pbmi, iUsage, ppvBits, hSection, dwOffset), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=CharSet.Auto)] private static extern IntPtr /*HBITMAP*/ IntCreateBitmap(int nWidth, int nHeight, int nPlanes, int nBitsPerPixel, IntPtr lpvBits); @@ -372,7 +372,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, return System.Internal.HandleCollector.Add(IntCreateBitmap(nWidth, nHeight, nPlanes, nBitsPerPixel, lpvBits), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=CharSet.Auto)] private static extern IntPtr /*HBITMAP*/ IntCreateBitmapShort(int nWidth, int nHeight, int nPlanes, int nBitsPerPixel, short[] lpvBits); @@ -381,7 +381,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, return System.Internal.HandleCollector.Add(IntCreateBitmapShort(nWidth, nHeight, nPlanes, nBitsPerPixel, lpvBits), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBitmap", CharSet=CharSet.Auto)] private static extern IntPtr /*HBITMAP*/ IntCreateBitmapByte(int nWidth, int nHeight, int nPlanes, int nBitsPerPixel, byte[] lpvBits); @@ -389,7 +389,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, public static IntPtr /*HBITMAP*/ CreateBitmap(int nWidth, int nHeight, int nPlanes, int nBitsPerPixel, byte[] lpvBits) { return System.Internal.HandleCollector.Add(IntCreateBitmapByte(nWidth, nHeight, nPlanes, nBitsPerPixel, lpvBits), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreatePatternBrush", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreatePatternBrush", CharSet=CharSet.Auto)] private static extern IntPtr /*HBRUSH*/ IntCreatePatternBrush(HandleRef hbmp); @@ -397,7 +397,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, public static IntPtr /*HBRUSH*/ CreatePatternBrush(HandleRef hbmp) { return System.Internal.HandleCollector.Add(IntCreatePatternBrush(hbmp), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBrushIndirect", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateBrushIndirect", CharSet=CharSet.Auto)] private static extern IntPtr IntCreateBrushIndirect(NativeMethods.LOGBRUSH lb); @@ -405,7 +405,7 @@ public static IntPtr CreateDIBSection(HandleRef hdc, HandleRef pbmi, int iUsage, public static IntPtr CreateBrushIndirect(NativeMethods.LOGBRUSH lb) { return System.Internal.HandleCollector.Add(IntCreateBrushIndirect(lb), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreatePen", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreatePen", CharSet=CharSet.Auto)] private static extern IntPtr IntCreatePen(int nStyle, int nWidth, int crColor); @@ -415,26 +415,26 @@ public static IntPtr CreatePen(int nStyle, int nWidth, int crColor) { } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool SetViewportExtEx(HandleRef hDC, int x, int y, NativeMethods.SIZE size); - [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, CharSet=CharSet.Auto)] public static extern IntPtr LoadCursor(HandleRef hInst, int iconId); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public extern static bool GetClipCursor([In, Out] ref NativeMethods.RECT lpRect); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr GetCursor(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool GetIconInfo(HandleRef hIcon, [In, Out] NativeMethods.ICONINFO info); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int IntersectClipRect(HandleRef hDC, int x1, int y1, int x2, int y2); - [DllImport(ExternDll.User32, ExactSpelling=true, EntryPoint="CopyImage", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, EntryPoint="CopyImage", CharSet=CharSet.Auto)] private static extern IntPtr IntCopyImage(HandleRef hImage, int uType, int cxDesired, int cyDesired, int fuFlags); public static IntPtr CopyImage(HandleRef hImage, int uType, int cxDesired, int cyDesired, int fuFlags) { @@ -445,7 +445,7 @@ public static IntPtr CopyImageAsCursor(HandleRef hImage, int uType, int cxDesire } - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool AdjustWindowRectEx(ref NativeMethods.RECT lpRect, int dwStyle, bool bMenu, int dwExStyle); @@ -454,25 +454,25 @@ public static IntPtr CopyImageAsCursor(HandleRef hImage, int uType, int cxDesire public static extern bool AdjustWindowRectExForDpi(ref NativeMethods.RECT lpRect, int dwStyle, bool bMenu, int dwExStyle, uint dpi); - [DllImport(ExternDll.Ole32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Ole32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int DoDragDrop(IComDataObject dataObject, UnsafeNativeMethods.IOleDropSource dropSource, int allowedEffects, int[] finalEffect); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr GetSysColorBrush(int nIndex); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool EnableWindow(HandleRef hWnd, bool enable); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool GetClientRect(HandleRef hWnd, [In, Out] ref NativeMethods.RECT rect); [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetDoubleClickTime(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetUpdateRgn(HandleRef hwnd, HandleRef hrgn, bool fErase); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool ValidateRect(HandleRef hWnd, [In, Out] ref NativeMethods.RECT rect); @@ -486,36 +486,36 @@ public static IntPtr CopyImageAsCursor(HandleRef hImage, int uType, int cxDesire // definition for GetLastError and calling it. The common language runtime can make internal calls to APIs that // overwrite the operating system maintained GetLastError. // - //[DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + //[DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] //public extern static int GetLastError(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int FillRect(HandleRef hdc, [In] ref NativeMethods.RECT rect, HandleRef hbrush); - [DllImport(ExternDll.Gdi32,ExactSpelling=true,CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32,ExactSpelling=true,CharSet=CharSet.Auto)] public static extern int /*COLORREF*/ GetTextColor(HandleRef hDC); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern int GetBkColor(HandleRef hDC); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int /*COLORREF*/ SetTextColor(HandleRef hDC, int crColor); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int SetBkColor(HandleRef hDC, int clr); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr /* HPALETTE */SelectPalette(HandleRef hdc, HandleRef hpal, int bForceBackground); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool SetViewportOrgEx(HandleRef hDC, int x, int y, [In, Out] NativeMethods.POINT point); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateRectRgn", CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, EntryPoint="CreateRectRgn", CharSet=CharSet.Auto)] private static extern IntPtr IntCreateRectRgn(int x1, int y1, int x2, int y2); @@ -523,60 +523,63 @@ public static IntPtr CopyImageAsCursor(HandleRef hImage, int uType, int cxDesire public static IntPtr CreateRectRgn(int x1, int y1, int x2, int y2) { return System.Internal.HandleCollector.Add(IntCreateRectRgn(x1, y1, x2, y2), NativeMethods.CommonHandles.GDI); } - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int CombineRgn(HandleRef hRgn, HandleRef hRgn1, HandleRef hRgn2, int nCombineMode); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int RealizePalette(HandleRef hDC); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool LPtoDP(HandleRef hDC, [In, Out] ref NativeMethods.RECT lpRect, int nCount); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool SetWindowOrgEx(HandleRef hDC, int x, int y, [In, Out] NativeMethods.POINT point); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool GetViewportOrgEx(HandleRef hDC, [In, Out] NativeMethods.POINT point); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int SetMapMode(HandleRef hDC, int nMapMode); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool IsWindowEnabled(HandleRef hWnd); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool IsWindowVisible(HandleRef hWnd); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool ReleaseCapture(); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetCurrentThreadId(); - [DllImport(ExternDll.User32, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] + [DllImport(ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)] public static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData); internal delegate bool EnumThreadWindowsCallback(IntPtr hWnd, IntPtr lParam); - [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId); [return:MarshalAs(UnmanagedType.Bool)] - [DllImport(ExternDll.Kernel32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern bool GetExitCodeThread(HandleRef hWnd, out int lpdwExitCode); - [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern bool ShowWindow(HandleRef hWnd, int nCmdShow); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] - public static extern bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter, - int x, int y, int cx, int cy, int flags); + public static extern bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter, int x, int y, int cx, int cy, int flags); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags); - [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, CharSet=CharSet.Auto)] public static extern int GetWindowTextLength(HandleRef hWnd); // this is a wrapper that comctl exposes for the NT function since it doesn't exist natively on 95. @@ -587,114 +590,122 @@ public static bool TrackMouseEvent(NativeMethods.TRACKMOUSEEVENT tme) { // only on NT - not on 95 - comctl32 has a wrapper for 95 and NT. return _TrackMouseEvent(tme); } - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool RedrawWindow(HandleRef hwnd, ref NativeMethods.RECT rcUpdate, HandleRef hrgnUpdate, int flags); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool RedrawWindow(HandleRef hwnd, NativeMethods.COMRECT rcUpdate, HandleRef hrgnUpdate, int flags); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool RedrawWindow(IntPtr hwnd, NativeMethods.COMRECT rcUpdate, IntPtr hrgnUpdate, int flags); + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool InvalidateRect(HandleRef hWnd, ref NativeMethods.RECT rect, bool erase); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool InvalidateRect(HandleRef hWnd, NativeMethods.COMRECT rect, bool erase); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool InvalidateRgn(HandleRef hWnd, HandleRef hrgn, bool erase); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool UpdateWindow(HandleRef hWnd); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetCurrentProcessId(); - [DllImport(ExternDll.User32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int ScrollWindowEx(HandleRef hWnd, int nXAmount, int nYAmount, NativeMethods.COMRECT rectScrollRegion, ref NativeMethods.RECT rectClip, HandleRef hrgnUpdate, ref NativeMethods.RECT prcUpdate, int flags); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetThreadLocale(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool MessageBeep(int type); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool DrawMenuBar(HandleRef hWnd); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public extern static bool IsChild(HandleRef parent, HandleRef child); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr SetTimer(HandleRef hWnd, int nIDEvent, int uElapse, IntPtr lpTimerFunc); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool KillTimer(HandleRef hwnd, int idEvent); - [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto), + [DllImport(ExternDll.User32, CharSet=CharSet.Auto), SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api")] public static extern int MessageBox(HandleRef hWnd, string text, string caption, int type); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr SelectObject(HandleRef hDC, HandleRef hObject); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetTickCount(); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool ScrollWindow(HandleRef hWnd, int nXAmount, int nYAmount, ref NativeMethods.RECT rectScrollRegion, ref NativeMethods.RECT rectClip); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr GetCurrentProcess(); - [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern IntPtr GetCurrentThread(); [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - [DllImport(ExternDll.Kernel32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Kernel32, ExactSpelling = true, CharSet = CharSet.Auto)] public extern static bool SetThreadLocale(int Locale); - [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern bool IsWindowUnicode(HandleRef hWnd); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool DrawEdge(HandleRef hDC, ref NativeMethods.RECT rect, int edge, int flags); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool DrawFrameControl(HandleRef hDC, ref NativeMethods.RECT rect, int type, int state); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetClipRgn(HandleRef hDC, HandleRef hRgn); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int GetRgnBox(HandleRef hRegion, ref NativeMethods.RECT clipRect); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int SelectClipRgn(HandleRef hDC, HandleRef hRgn); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int SetROP2(HandleRef hDC, int nDrawMode); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool DrawIcon(HandleRef hDC, int x, int y, HandleRef hIcon); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool DrawIconEx(HandleRef hDC, int x, int y, HandleRef hIcon, int width, int height, int iStepIfAniCursor, HandleRef hBrushFlickerFree, int diFlags); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern int SetBkMode(HandleRef hDC, int nBkMode); - [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, SetLastError=true, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool BitBlt(HandleRef hDC, int x, int y, int nWidth, int nHeight, HandleRef hSrcDC, int xSrc, int ySrc, int dwRop); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.Gdi32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop); + + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool ShowCaret(HandleRef hWnd); - [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] + [DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)] public static extern bool HideCaret(HandleRef hWnd); [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] @@ -880,7 +891,7 @@ public static int ColorToCOLORREF(Color color) { return (int)color.R | ((int)color.G << 8) | ((int)color.B << 16); } - [ComImport(), Guid("BEF6E003-A874-101A-8BBA-00AA00300CAB"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIDispatch)] + [ComImport(), Guid("BEF6E003-A874-101A-8BBA-00AA00300CAB"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface IFontDisp { string Name {get; set;} @@ -899,6 +910,18 @@ public interface IFontDisp { short Charset {get;set;} } + + [DllImport(ExternDll.Gdi32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool RoundRect(HandleRef hDC, int left, int top, int right, int bottom, int width, int height); + + [DllImport(ExternDll.Gdi32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetTextMetrics(HandleRef hdc, NativeMethods.TEXTMETRIC tm); + + [ResourceExposure(ResourceScope.None)] + [DllImport(ExternDll.Uxtheme, CharSet = CharSet.Auto)] + public extern static int SetWindowTheme(IntPtr hWnd, string subAppName, string subIdList); } } diff --git a/src/Common/src/UnsafeNativeMethods.cs b/src/Common/src/UnsafeNativeMethods.cs index 47d82801884..86ecfb73baa 100644 --- a/src/Common/src/UnsafeNativeMethods.cs +++ b/src/Common/src/UnsafeNativeMethods.cs @@ -197,7 +197,7 @@ internal static bool IsVista [DllImport(ExternDll.Ole32)] public static extern int CoGetMalloc(int dwReserved, out IMalloc pMalloc); - + /* Commenting this out until someone actually needs to use it again... [DllImport(ExternDll.Ole32)] public static extern int OleSetMenuDescriptor(IntPtr hOleMenu, IntPtr hWndFrame, IntPtr hWndActiveObject, IOleInPlaceFrame frame, IOleInPlaceActiveObject activeObject); @@ -208,6 +208,14 @@ internal static bool IsVista public static extern bool IsBadReadPtr(HandleRef ptr, int size); */ + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetMessageTime(); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [ResourceExposure(ResourceScope.Process)] + public static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId); + [DllImport(ExternDll.Comdlg32, SetLastError=true, CharSet=CharSet.Auto)] public static extern bool PageSetupDlg([In, Out] NativeMethods.PAGESETUPDLG lppsd); diff --git a/src/System.Windows.Forms.Design.Editors/src/System/ComponentModel/Design/ObjectSelectorEditor.cs b/src/System.Windows.Forms.Design.Editors/src/System/ComponentModel/Design/ObjectSelectorEditor.cs index 1f16aa9beb9..d4e7f2f1fa0 100644 --- a/src/System.Windows.Forms.Design.Editors/src/System/ComponentModel/Design/ObjectSelectorEditor.cs +++ b/src/System.Windows.Forms.Design.Editors/src/System/ComponentModel/Design/ObjectSelectorEditor.cs @@ -79,7 +79,7 @@ public static void ApplyTreeViewThemeStyles(TreeView treeView) { if (treeView == null) { - throw new ArgumentNullException("treeView"); + throw new ArgumentNullException(nameof(treeView)); } treeView.HotTracking = true; diff --git a/src/System.Windows.Forms.Design/src/Resources/SR.Designer.cs b/src/System.Windows.Forms.Design/src/Resources/SR.Designer.cs new file mode 100644 index 00000000000..9dc46dd2d42 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/Resources/SR.Designer.cs @@ -0,0 +1,936 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Windows.Forms.Design.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class SR { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SR() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Windows.Forms.Design.Resources.SR", typeof(SR).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Copy and move {0}. + /// + public static string BehaviorServiceCopyControl { + get { + return ResourceManager.GetString("BehaviorServiceCopyControl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy and move {0} Controls. + /// + public static string BehaviorServiceCopyControls { + get { + return ResourceManager.GetString("BehaviorServiceCopyControls", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move {0}. + /// + public static string BehaviorServiceMoveControl { + get { + return ResourceManager.GetString("BehaviorServiceMoveControl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move {0} Controls. + /// + public static string BehaviorServiceMoveControls { + get { + return ResourceManager.GetString("BehaviorServiceMoveControls", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Double cannot be converted to a date.. + /// + public static string CannotConvertDoubleToDate { + get { + return ResourceManager.GetString("CannotConvertDoubleToDate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Integer cannot be converted to a float.. + /// + public static string CannotConvertIntToFloat { + get { + return ResourceManager.GetString("CannotConvertIntToFloat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unhandled VT: {0}.. + /// + public static string COM2UnhandledVT { + get { + return ResourceManager.GetString("COM2UnhandledVT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format {0} components (alignment). + /// + public static string CommandSetAlignByPrimary { + get { + return ResourceManager.GetString("CommandSetAlignByPrimary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Align {0} components to grid. + /// + public static string CommandSetAlignToGrid { + get { + return ResourceManager.GetString("CommandSetAlignToGrid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cut {0} Components. + /// + public static string CommandSetCutMultiple { + get { + return ResourceManager.GetString("CommandSetCutMultiple", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete {0} components. + /// + public static string CommandSetDelete { + get { + return ResourceManager.GetString("CommandSetDelete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while processing this command.\r\n{0}. + /// + public static string CommandSetError { + get { + return ResourceManager.GetString("CommandSetError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format {0} components (spacing). + /// + public static string CommandSetFormatSpacing { + get { + return ResourceManager.GetString("CommandSetFormatSpacing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Paste components. + /// + public static string CommandSetPaste { + get { + return ResourceManager.GetString("CommandSetPaste", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size {0} components. + /// + public static string CommandSetSize { + get { + return ResourceManager.GetString("CommandSetSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size {0} components to grid. + /// + public static string CommandSetSizeToGrid { + get { + return ResourceManager.GetString("CommandSetSizeToGrid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown spacing command. + /// + public static string CommandSetUnknownSpacingCommand { + get { + return ResourceManager.GetString("CommandSetUnknownSpacingCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adding event '{0}'. + /// + public static string ComponentDesignerAddEvent { + get { + return ResourceManager.GetString("ComponentDesignerAddEvent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not convert value '{0}' to the type '{1}'.. + /// + public static string DesignerActionPanel_CouldNotConvertValue { + get { + return ResourceManager.GetString("DesignerActionPanel_CouldNotConvertValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find method '{0}'. + /// + public static string DesignerActionPanel_CouldNotFindMethod { + get { + return ResourceManager.GetString("DesignerActionPanel_CouldNotFindMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find property '{0}' on '{1}'.. + /// + public static string DesignerActionPanel_CouldNotFindProperty { + get { + return ResourceManager.GetString("DesignerActionPanel_CouldNotFindProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} Tasks. + /// + public static string DesignerActionPanel_DefaultPanelTitle { + get { + return ResourceManager.GetString("DesignerActionPanel_DefaultPanelTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error using the dropdown: {0}. + /// + public static string DesignerActionPanel_ErrorActivatingDropDown { + get { + return ResourceManager.GetString("DesignerActionPanel_ErrorActivatingDropDown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error invoking '{0}'. Details: {1}. + /// + public static string DesignerActionPanel_ErrorInvokingAction { + get { + return ResourceManager.GetString("DesignerActionPanel_ErrorInvokingAction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error setting value '{0}' to property '{1}'. Details: {2}. + /// + public static string DesignerActionPanel_ErrorSettingValue { + get { + return ResourceManager.GetString("DesignerActionPanel_ErrorSettingValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Call to BeginDrag must succeed before calling drag functions.. + /// + public static string DesignerBeginDragNotCalled { + get { + return ResourceManager.GetString("DesignerBeginDragNotCalled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot remove or destroy inherited component '{0}'.. + /// + public static string DesignerHostCantDestroyInheritedComponent { + get { + return ResourceManager.GetString("DesignerHostCantDestroyInheritedComponent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot add an instance of {0} to the designer because it would create a circular dependency. Make sure that the type does not have the same namespace and type name as the root component {1}.. + /// + public static string DesignerHostCyclicAdd { + get { + return ResourceManager.GetString("DesignerHostCyclicAdd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failure initializing the designer. It has no Component member.. + /// + public static string DesignerHostDesignerNeedsComponent { + get { + return ResourceManager.GetString("DesignerHostDesignerNeedsComponent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleting '{0}'.. + /// + public static string DesignerHostDestroyComponentTransaction { + get { + return ResourceManager.GetString("DesignerHostDestroyComponentTransaction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name {0} is already in use by another component.. + /// + public static string DesignerHostDuplicateName { + get { + return ResourceManager.GetString("DesignerHostDuplicateName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Component of type {0} could not be created. Make sure the type implements IComponent and provides an appropriate public constructor. Appropriate constructors either take no parameters or take a single IContainer parameter.. + /// + public static string DesignerHostFailedComponentCreate { + get { + return ResourceManager.GetString("DesignerHostFailedComponentCreate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Description Available. + /// + public static string DesignerHostGenericTransactionName { + get { + return ResourceManager.GetString("DesignerHostGenericTransactionName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A designer loader has already been configured for this design surface.. + /// + public static string DesignerHostLoaderSpecified { + get { + return ResourceManager.GetString("DesignerHostLoaderSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The designer transaction '{0}' cannot be committed or canceled because nested transaction '{1}' is still active. Commit or cancel the nested transaction first.. + /// + public static string DesignerHostNestedTransaction { + get { + return ResourceManager.GetString("DesignerHostNestedTransaction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot open a designer for the file because the class within it does not inherit from a class that can be visually designed.. + /// + public static string DesignerHostNoBaseClass { + get { + return ResourceManager.GetString("DesignerHostNoBaseClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no designer for the class {0}.. + /// + public static string DesignerHostNoTopLevelDesigner { + get { + return ResourceManager.GetString("DesignerHostNoTopLevelDesigner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New components cannot be added while a designer is unloading.. + /// + public static string DesignerHostUnloading { + get { + return ResourceManager.GetString("DesignerHostUnloading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inherited control. + /// + public static string DesignerInherited { + get { + return ResourceManager.GetString("DesignerInherited", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inherited control (Private). + /// + public static string DesignerInheritedReadOnly { + get { + return ResourceManager.GetString("DesignerInheritedReadOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Optimized Code Generation. + /// + public static string DesignerOptions_CodeGenDisplay { + get { + return ResourceManager.GetString("DesignerOptions_CodeGenDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Code Generation Settings. + /// + public static string DesignerOptions_CodeGenSettings { + get { + return ResourceManager.GetString("DesignerOptions_CodeGenSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to InSitu Editing. + /// + public static string DesignerOptions_EnableInSituEditingCat { + get { + return ResourceManager.GetString("DesignerOptions_EnableInSituEditingCat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether InSitu editing is enabled.. + /// + public static string DesignerOptions_EnableInSituEditingDesc { + get { + return ResourceManager.GetString("DesignerOptions_EnableInSituEditingDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable InSitu Editing. + /// + public static string DesignerOptions_EnableInSituEditingDisplay { + get { + return ResourceManager.GetString("DesignerOptions_EnableInSituEditingDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the default X and Y grid setting on designers when LayoutMode = SnapToGrid.. + /// + public static string DesignerOptions_GridSizeDesc { + get { + return ResourceManager.GetString("DesignerOptions_GridSizeDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default Grid Cell Size. + /// + public static string DesignerOptions_GridSizeDisplayName { + get { + return ResourceManager.GetString("DesignerOptions_GridSizeDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Layout Settings. + /// + public static string DesignerOptions_LayoutSettings { + get { + return ResourceManager.GetString("DesignerOptions_LayoutSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether designer smart tag windows should be shown by default.. + /// + public static string DesignerOptions_ObjectBoundSmartTagAutoShow { + get { + return ResourceManager.GetString("DesignerOptions_ObjectBoundSmartTagAutoShow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Automatically Open Smart Tags. + /// + public static string DesignerOptions_ObjectBoundSmartTagAutoShowDisplayName { + get { + return ResourceManager.GetString("DesignerOptions_ObjectBoundSmartTagAutoShowDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Object Bound Smart Tag Settings. + /// + public static string DesignerOptions_ObjectBoundSmartTagSettings { + get { + return ResourceManager.GetString("DesignerOptions_ObjectBoundSmartTagSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enabled optimized code generation. Some controls may not be compatible with this mode. For this change to take effect, Visual Studio must be closed and re-opened.. + /// + public static string DesignerOptions_OptimizedCodeGen { + get { + return ResourceManager.GetString("DesignerOptions_OptimizedCodeGen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether designers should display a sizing grid when LayoutMode = SnapToGrid.. + /// + public static string DesignerOptions_ShowGridDesc { + get { + return ResourceManager.GetString("DesignerOptions_ShowGridDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Grid. + /// + public static string DesignerOptions_ShowGridDisplayName { + get { + return ResourceManager.GetString("DesignerOptions_ShowGridDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether designers should snap to grid dots when LayoutMode = SnapToGrid.. + /// + public static string DesignerOptions_SnapToGridDesc { + get { + return ResourceManager.GetString("DesignerOptions_SnapToGridDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Snap to Grid. + /// + public static string DesignerOptions_SnapToGridDisplayName { + get { + return ResourceManager.GetString("DesignerOptions_SnapToGridDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether designers should show smart tag popup windows.. + /// + public static string DesignerOptions_UseSmartTags { + get { + return ResourceManager.GetString("DesignerOptions_UseSmartTags", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controls whether designers should use snap lines. If true, snap lines will be used as guides. If false, grid lines will be used.. + /// + public static string DesignerOptions_UseSnapLines { + get { + return ResourceManager.GetString("DesignerOptions_UseSnapLines", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The container cannot be disposed at design time.. + /// + public static string DesignSurfaceContainerDispose { + get { + return ResourceManager.GetString("DesignSurfaceContainerDispose", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create a view for this design surface because the designer is not loaded.. + /// + public static string DesignSurfaceDesignerNotLoaded { + get { + return ResourceManager.GetString("DesignSurfaceDesignerNotLoaded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while loading the document. Fix the error, and then try loading the document again. The error message follows:\r\n\r\n{0}. + /// + public static string DesignSurfaceFatalError { + get { + return ResourceManager.GetString("DesignSurfaceFatalError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The designer loader did not provide a root component but has not indicated why.. + /// + public static string DesignSurfaceNoRootComponent { + get { + return ResourceManager.GetString("DesignSurfaceNoRootComponent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The designer loaded, but it does not offer a view compatible with this design surface.. + /// + public static string DesignSurfaceNoSupportedTechnology { + get { + return ResourceManager.GetString("DesignSurfaceNoSupportedTechnology", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The service {0} cannot be removed from the service container.. + /// + public static string DesignSurfaceServiceIsFixed { + get { + return ResourceManager.GetString("DesignSurfaceServiceIsFixed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to .NET Component. + /// + public static string DotNET_ComponentType { + get { + return ResourceManager.GetString("DotNET_ComponentType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Drag {0} components. + /// + public static string DragDropDragComponents { + get { + return ResourceManager.GetString("DragDropDragComponents", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move {0}. + /// + public static string DragDropMoveComponent { + get { + return ResourceManager.GetString("DragDropMoveComponent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move {0} components. + /// + public static string DragDropMoveComponents { + get { + return ResourceManager.GetString("DragDropMoveComponents", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This IDataObject doesn't support SetData.. + /// + public static string DragDropSetDataError { + get { + return ResourceManager.GetString("DragDropSetDataError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size {0}. + /// + public static string DragDropSizeComponent { + get { + return ResourceManager.GetString("DragDropSizeComponent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size {0} components. + /// + public static string DragDropSizeComponents { + get { + return ResourceManager.GetString("DragDropSizeComponents", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The extender provider {0} has already been added as an extender. Adding another would result in duplicate properties.. + /// + public static string ExtenderProviderServiceDuplicateProvider { + get { + return ResourceManager.GetString("ExtenderProviderServiceDuplicateProvider", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Read-Only. + /// + public static string InheritanceServiceReadOnlyCollection { + get { + return ResourceManager.GetString("InheritanceServiceReadOnlyCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This method/object is not implemented by design.. + /// + public static string NotImplementedByDesign { + get { + return ResourceManager.GetString("NotImplementedByDesign", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Winforms Designer is not supported on this platform.. + /// + public static string PlatformNotSupported_WinformsDesigner { + get { + return ResourceManager.GetString("PlatformNotSupported_WinformsDesigner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RTL_False. + /// + public static string RTL { + get { + return ResourceManager.GetString("RTL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument should be a non-empty string.. + /// + public static string ToolboxItemInvalidKey { + get { + return ResourceManager.GetString("ToolboxItemInvalidKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property {0} requires an argument of type {1}.. + /// + public static string ToolboxItemInvalidPropertyType { + get { + return ResourceManager.GetString("ToolboxItemInvalidPropertyType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toolbox item cannot be modified.. + /// + public static string ToolboxItemLocked { + get { + return ResourceManager.GetString("ToolboxItemLocked", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Data type {0} is not serializable. Items added to a property dictionary must be serializable.. + /// + public static string ToolboxItemValueNotSerializable { + get { + return ResourceManager.GetString("ToolboxItemValueNotSerializable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Auto Arrange Tray Icons. + /// + public static string TrayAutoArrange { + get { + return ResourceManager.GetString("TrayAutoArrange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line Up Tray Icons. + /// + public static string TrayLineUpIcons { + get { + return ResourceManager.GetString("TrayLineUpIcons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Large or Small Icons. + /// + public static string TrayShowLargeIcons { + get { + return ResourceManager.GetString("TrayShowLargeIcons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error. + /// + public static string UIServiceHelper_ErrorCaption { + get { + return ResourceManager.GetString("UIServiceHelper_ErrorCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add Component. + /// + public static string UndoEngineComponentAdd0 { + get { + return ResourceManager.GetString("UndoEngineComponentAdd0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add {0}. + /// + public static string UndoEngineComponentAdd1 { + get { + return ResourceManager.GetString("UndoEngineComponentAdd1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change Component. + /// + public static string UndoEngineComponentChange0 { + get { + return ResourceManager.GetString("UndoEngineComponentChange0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change {0}. + /// + public static string UndoEngineComponentChange1 { + get { + return ResourceManager.GetString("UndoEngineComponentChange1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change {0}.{1}. + /// + public static string UndoEngineComponentChange2 { + get { + return ResourceManager.GetString("UndoEngineComponentChange2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove Component. + /// + public static string UndoEngineComponentRemove0 { + get { + return ResourceManager.GetString("UndoEngineComponentRemove0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove {0}. + /// + public static string UndoEngineComponentRemove1 { + get { + return ResourceManager.GetString("UndoEngineComponentRemove1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rename {0} to {1}. + /// + public static string UndoEngineComponentRename { + get { + return ResourceManager.GetString("UndoEngineComponentRename", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The service {0} is required but could not be found. If you have removed this service ensure that you provide a replacement.. + /// + public static string UndoEngineMissingService { + get { + return ResourceManager.GetString("UndoEngineMissingService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not implemented.. + /// + public static string UnsafeNativeMethodsNotImplemented { + get { + return ResourceManager.GetString("UnsafeNativeMethodsNotImplemented", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <couldn't find resource string "WindowsFormsAddEvent">. + /// + public static string WindowsFormsAddEvent { + get { + return ResourceManager.GetString("WindowsFormsAddEvent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Horizontal center of {0} component(s). + /// + public static string WindowsFormsCommandCenterX { + get { + return ResourceManager.GetString("WindowsFormsCommandCenterX", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Vertical center of {0} component(s). + /// + public static string WindowsFormsCommandCenterY { + get { + return ResourceManager.GetString("WindowsFormsCommandCenterY", resourceCulture); + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/Resources/SR.resx b/src/System.Windows.Forms.Design/src/Resources/SR.resx index c76994d4e97..9c2ff7e65d1 100644 --- a/src/System.Windows.Forms.Design/src/Resources/SR.resx +++ b/src/System.Windows.Forms.Design/src/Resources/SR.resx @@ -214,7 +214,116 @@ Not implemented. - Could not find method '{0}' + Could not find method '{0}'. + + + Format {0} components (alignment) + + + Align {0} components to grid + + + Cut {0} Components + + + Delete {0} components + + + An error occurred while processing this command.\r\n{0} + + + Format {0} components (spacing) + + + Paste components + + + Size {0} components + + + Size {0} components to grid + + + Unknown spacing command + + + Move {0} + + + Move {0} components + + + Horizontal center of {0} component(s) + + + Vertical center of {0} component(s) + + + Could not convert value '{0}' to the type '{1}'. + + + Could not find property '{0}' on '{1}'. + + + {0} Tasks + + + Error using the dropdown: {0} + + + Error invoking '{0}'. Details: {1} + + + Error setting value '{0}' to property '{1}'. Details: {2} + + + Inherited control + + + Inherited control (Private) + + + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + + + Auto Arrange Tray Icons + + + Line Up Tray Icons + + + Show Large or Small Icons + + + Error + + + <couldn't find resource string "WindowsFormsAddEvent"> + + + Copy and move {0} + + + Copy and move {0} Controls + + + Move {0} + + + Move {0} Controls + + + Call to BeginDrag must succeed before calling drag functions. + + + Drag {0} components + + + Size {0} + + + Size {0} components Add Component @@ -375,4 +484,4 @@ The serialization store is closed. New objects cannot be added to a closed store. - + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/System/WinForms/Design/InheritedGlyph.bmp b/src/System.Windows.Forms.Design/src/Resources/System/WinForms/Design/InheritedGlyph.bmp new file mode 100644 index 00000000000..0e493efc97c Binary files /dev/null and b/src/System.Windows.Forms.Design/src/Resources/System/WinForms/Design/InheritedGlyph.bmp differ diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.cs.xlf index f00bd3ca529..af5ec675106 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.cs.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Úložiště serializace je zavřeno. Do zavřeného úložiště nelze přidávat nové objekty. @@ -37,6 +57,106 @@ Nelze převést typ Integer na typ Float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Kontejner nelze uvolnit v době návrhu. @@ -67,11 +187,6 @@ Službu {0} nelze odebrat z kontejneru služeb. - - Could not find method '{0}' - Nepovedlo se najít metodu {0}. - - Cannot remove or destroy inherited component '{0}'. Zděděnou součást {0} nelze odebrat nebo zrušit. @@ -227,6 +342,21 @@ Komponenta .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Tento objekt IDataObject nepodporuje funkci SetData. @@ -242,6 +372,16 @@ Jen pro čtení + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Tuto metodu nebo objekt návrh neimplementuje. @@ -252,6 +392,11 @@ Winforms Designer není na této platformě podporován. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Nelze vytvořit novou relaci, protože tento správce serializace již obsahuje aktivní relaci serializace. @@ -427,11 +572,46 @@ Služba {0} je požadována, ale nebyla nalezena. Pokud jste tuto službu odebrali, zajistěte její náhradu. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Není implementováno. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.de.xlf index 159cdb6d546..8ae18c8bcae 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.de.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Der Serialisierungsspeicher ist geschlossen. Neue Objekte können nicht zu einem geschlossenen Speicher hinzugefügt werden. @@ -37,6 +57,106 @@ "Integer" kann nicht in "Float" konvertiert werden. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Der Container kann nicht zur Entwurfszeit verworfen werden. @@ -67,11 +187,6 @@ Der Dienst {0} kann nicht aus dem Dienstcontainer entfernt werden. - - Could not find method '{0}' - {0}-Methode konnte nicht gefunden werden. - - Cannot remove or destroy inherited component '{0}'. Die vererbte Komponente {0} kann nicht gelöscht werden. @@ -227,6 +342,21 @@ .NET-Komponente + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Dieses IDataObject unterstützt SetData nicht. @@ -242,6 +372,16 @@ Schreibgeschützt + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Diese Methode/dieses Objekt wird entwurfsbedingt nicht implementiert. @@ -252,6 +392,11 @@ Der Windows Forms-Designer wird auf dieser Plattform nicht unterstützt. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Sie können keine neue Sitzung erstellen, da dieser Serialisierungs-Manager bereits eine aktive Serialisierungssitzung besitzt. @@ -427,11 +572,46 @@ Der Dienst {0} ist erforderlich, wurde aber nicht gefunden. Wenn Sie den Dienst entfernt haben, stellen Sie sicher, dass ein Ersatz verfügbar ist. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Nicht implementiert. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.es.xlf index ef492b1c52d..e09c047ee72 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.es.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. El almacén de serialización está cerrado. No se pueden agregar nuevos objetos a un almacén cerrado. @@ -37,6 +57,106 @@ No se puede convertir integer en float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. El contenedor no se puede desechar en tiempo de diseño. @@ -67,11 +187,6 @@ No se puede eliminar el servicio {0} del contenedor de servicios. - - Could not find method '{0}' - No se encontró el método "{0}" - - Cannot remove or destroy inherited component '{0}'. No se puede quitar ni destruir un componente '{0}' heredado. @@ -227,6 +342,21 @@ Componente .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. IDataObject no es compatible con SetData. @@ -242,6 +372,16 @@ Solo lectura + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Este objeto o método no se implementa por diseño. @@ -252,6 +392,11 @@ Winforms Designer no se admite en esta plataforma. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. No puede crear una nueva sesión porque este administrador de serialización ya tiene una sesión de serialización activa. @@ -427,11 +572,46 @@ El servicio {0} es obligatorio pero no se pudo encontrar. Si ha quitado este servicio, asegúrese de proporcionar un reemplazo. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Sin implementar. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.fr.xlf index 82d886050c4..fb8651d6c10 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.fr.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Le magasin de sérialisation est fermé. Impossible d'ajouter de nouveaux objets à un magasin fermé. @@ -37,6 +57,106 @@ Impossible de convertir une valeur integer en float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Le conteneur ne peut pas être supprimé au moment du design. @@ -67,11 +187,6 @@ Impossible de supprimer le service {0} dans le conteneur de service. - - Could not find method '{0}' - Méthode '{0}' introuvable - - Cannot remove or destroy inherited component '{0}'. Impossible d'enlever ou de détruire le composant hérité '{0}'. @@ -227,6 +342,21 @@ Composant .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Ce IDataObject ne prend pas en charge SetData. @@ -242,6 +372,16 @@ En lecture seule + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Cette méthode ou cet objet n'est pas implémenté(e) dans la conception. @@ -252,6 +392,11 @@ Le concepteur WinForms n'est pas pris en charge sur cette plateforme. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Vous ne pouvez pas créer une nouvelle session, car ce gestionnaire de sérialisation dispose déjà d'une session de sérialisation active. @@ -427,11 +572,46 @@ Le service {0} requis est introuvable. Si vous avez supprimé ce service, assurez-vous qu'un service de remplacement est fourni. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Non implémenté. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.it.xlf index e0b9a585395..336fd155906 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.it.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. L'archivio di serializzazione è chiuso. Non è possibile aggiungere nuovi oggetti a un archivio chiuso. @@ -37,6 +57,106 @@ Impossibile convertire un numero intero in un valore float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Impossibile eliminare il contenitore in fase di progettazione. @@ -67,11 +187,6 @@ Impossibile rimuovere il servizio {0} dal contenitore dei servizi. - - Could not find method '{0}' - Non è stato possibile trovare il metodo '{0}' - - Cannot remove or destroy inherited component '{0}'. Impossibile rimuovere o eliminare in modo permanente il componente ereditato '{0}'. @@ -227,6 +342,21 @@ Componente .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Questo oggetto IDataObject non supporta SetData. @@ -242,6 +372,16 @@ Sola lettura + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Questo metodo/oggetto non è implementato per impostazione predefinita. @@ -252,6 +392,11 @@ Winforms Designer non è supportato in questa piattaforma. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Non è possibile creare una nuova sessione perché per questo gestore di serializzazione esiste già una sessione di serializzazione attiva. @@ -427,11 +572,46 @@ Impossibile trovare il servizio richiesto {0}. Se tale servizio è stato rimosso, sarà necessario sostituirlo. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Non implementato. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ja.xlf index ebbcc02a293..0d4fff83830 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ja.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. シリアル化ストアは閉じられています。 閉じられたストアに新しいオブジェクトを追加することはできません。 @@ -37,6 +57,106 @@ integer を float に変換できません。 + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. デザイン時に、コンテナーを破棄することはできません。 @@ -67,11 +187,6 @@ サービス {0} をサービス コンテナーから削除できません。 - - Could not find method '{0}' - メソッド '{0}' が見つかりませんでした - - Cannot remove or destroy inherited component '{0}'. 継承コンポーネント '{0}' を削除または破棄できません。 @@ -227,6 +342,21 @@ .NET コンポーネント + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. この IDataObject は SetData をサポートしていません。 @@ -242,6 +372,16 @@ 読み取り専用 + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. このメソッド/オブジェクトはデザインによって実装されていません。 @@ -252,6 +392,11 @@ Windows フォーム デザイナーは、このプラットフォームではサポートされていません。 + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. このシリアル化マネージャーにはアクティブなシリアル化セッションが既に存在するため、新しいセッションを作成できません。 @@ -427,11 +572,46 @@ サービス {0} が必要ですが、見つかりませんでした。 このサービスを削除した場合、必ず置換したサービスを指定してください。 + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. 実装されていません。 + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ko.xlf index 198f3d5a71a..54a3db1843b 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ko.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Serialization 저장소가 닫혀 있습니다. 닫힌 저장소에는 새 개체를 추가할 수 없습니다. @@ -37,6 +57,106 @@ integer 형식을 float 형식으로 변환할 수 없습니다. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. 컨테이너를 디자인 타임에 삭제할 수 없습니다. @@ -67,11 +187,6 @@ 서비스 컨테이너에서 {0} 서비스를 제거할 수 없습니다. - - Could not find method '{0}' - '{0}' 메서드를 찾을 수 없습니다. - - Cannot remove or destroy inherited component '{0}'. 상속된 구성 요소 '{0}'을(를) 제거하거나 소멸시킬 수 없습니다. @@ -227,6 +342,21 @@ .NET 구성 요소 + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. IDataObject는 SetData를 지원하지 않습니다. @@ -242,6 +372,16 @@ 읽기 전용 + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. 이 메서드/개체는 의도적으로 구현되지 않습니다. @@ -252,6 +392,11 @@ Winforms 디자이너는 이 플랫폼에서 지원되지 않습니다. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. 이 serialization 관리자에 이미 활성 serialization 세션이 있으므로 새 세션을 만들 수 없습니다. @@ -427,11 +572,46 @@ 필요한 {0} 서비스를 찾을 수 없습니다. 이 서비스를 제거한 경우에는 대체할 서비스를 제공해야 합니다. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. 구현되지 않았습니다. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pl.xlf index 3e1725e0b4a..5c6110864a4 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pl.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Magazyn serializacji jest zamknięty. Do zamkniętego magazynu nie można dodawać nowych obiektów. @@ -37,6 +57,106 @@ Nie można przekonwertować typu integer na typ float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Kontenera nie można usunąć podczas projektowania. @@ -67,11 +187,6 @@ Nie można usunąć usługi {0} z kontenera usług. - - Could not find method '{0}' - Nie można znaleźć metody „{0}”. - - Cannot remove or destroy inherited component '{0}'. Nie można usunąć ani zniszczyć składnika dziedziczonego '{0}'. @@ -227,6 +342,21 @@ Składnik platformy .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Ten element IDataObject nie obsługuje elementu SetData. @@ -242,6 +372,16 @@ Tylko do odczytu + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Ta metoda/obiekt nie jest domyślnie implementowana. @@ -252,6 +392,11 @@ Projektant formularzy systemu Windows nie jest obsługiwany na tej platformie. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Nie można utworzyć nowej sesji, ponieważ jest już aktywna sesja serializacji tego menedżera. @@ -427,11 +572,46 @@ Usługa {0} jest wymagana, ale nie można jej znaleźć. Jeśli została usunięta, pamiętaj o zapewnieniu jej zamiennika. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Nie zaimplementowano. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pt-BR.xlf index 8fb0a0039a3..68e633ff016 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.pt-BR.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. O repositório de serialização está fechado. Não é possível adicionar novos objetos a um repositório fechado. @@ -37,6 +57,106 @@ Não é possível converter inteiro em flutuante. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. O contêiner não pode ser descartado em tempo de design. @@ -67,11 +187,6 @@ O serviço {0} não pode ser removido do contêiner de serviços. - - Could not find method '{0}' - Não foi possível encontrar o método '{0}' - - Cannot remove or destroy inherited component '{0}'. Não é possível remover ou destruir o componente herdado '{0}'. @@ -227,6 +342,21 @@ Componente .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Este IDataObject não oferece suporte para SetData. @@ -242,6 +372,16 @@ Somente Leitura + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Este método/objeto não é implementado por design. @@ -252,6 +392,11 @@ O Designer de Formulários do Windows não é compatível com esta plataforma. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Você não pode criar uma nova sessão porque esse gerente de serialização já possui uma sessão de serialização ativa. @@ -427,11 +572,46 @@ O serviço {0} é necessário, mas não foi encontrado. Se você tiver removido esse serviço, forneça um substituto. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Não implementado. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ru.xlf index 3e187732fa1..7e850c29eaf 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.ru.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Хранилище сериализованных объектов закрыто. Добавление новых объектов в закрытое хранилище невозможно. @@ -37,6 +57,106 @@ Тип integer нельзя привести к типу float. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Контейнер не может быть освобожден во время конструирования. @@ -67,11 +187,6 @@ Служба {0} не может быть удалена из контейнера служб. - - Could not find method '{0}' - Не удалось найти метод "{0}". - - Cannot remove or destroy inherited component '{0}'. Невозможно удалить или уничтожить унаследованный компонент '{0}'. @@ -227,6 +342,21 @@ Компонент .NET + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. Этот объект IDataObject не поддерживает SetData. @@ -242,6 +372,16 @@ Только для чтения + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Этот метод или объект не реализован намеренно. @@ -252,6 +392,11 @@ Конструктор Windows Forms не поддерживается на этой платформе. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Вы не можете создать новый сеанс, так как у этого диспетчера сериализации уже есть активный сеанс сериализации. @@ -427,11 +572,46 @@ Требуется служба {0}, но она не найдена. Если вы перенесли эту службу, убедитесь в том, что обеспечили замену. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Не реализовано. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.tr.xlf index 7c47b4d33a2..55a018fc8da 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.tr.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. Serileştirme deposu kapalı. Kapalı depoya yeni nesneler eklenemez. @@ -37,6 +57,106 @@ Tam sayı kayan noktalıya dönüştürülemez. + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. Kapsayıcı tasarım sırasında atılamaz. @@ -67,11 +187,6 @@ {0} hizmeti, hizmet kapsayıcısından kaldırılamıyor. - - Could not find method '{0}' - '{0}' metodu bulunamadı - - Cannot remove or destroy inherited component '{0}'. Devralınan '{0}' bileşeni kaldırılamaz veya yok edilemez. @@ -227,6 +342,21 @@ .NET Bileşeni + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. IDataObject SetData'yı desteklemiyor. @@ -242,6 +372,16 @@ Salt Okunur + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. Tasarım gereği, bu metot/nesne uygulanmadı. @@ -252,6 +392,11 @@ Winforms Tasarımcısı bu platformda desteklenmiyor. + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. Bu serileştirme yöneticisinin zaten etkin bir serileştirme oturumu bulunduğundan yeni oturum oluşturamazsınız. @@ -427,11 +572,46 @@ {0} hizmeti gerekiyordu ancak bulunamadı. Bu hizmeti kaldırdıysanız bunun yerine geçecek bir hizmet sağladığınızdan emin olun. + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. Uygulanmadı. + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hans.xlf index 071ed300bb0..ece3e501d01 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hans.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. 序列化存储区已关闭。新对象无法添加到已关闭的存储区中。 @@ -37,6 +57,106 @@ 无法将 integer 转换为 float。 + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. 不能在设计时释放容器。 @@ -67,11 +187,6 @@ 无法将服务 {0} 从服务容器移除。 - - Could not find method '{0}' - 未能找到方法“{0}” - - Cannot remove or destroy inherited component '{0}'. 无法移除或损坏继承的组件“{0}”。 @@ -227,6 +342,21 @@ .NET 组件 + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. 此 IDataObject 不支持 SetData。 @@ -242,6 +372,16 @@ 只读 + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. 未按设计实现此方法/对象。 @@ -252,6 +392,11 @@ 此平台上不支持 Winforms Designer。 + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. 此序列化管理器已有活动的序列化会话,因此不能创建新的会话。 @@ -427,11 +572,46 @@ 未能找到所需服务 {0}。如果您移除了此服务,请确保提供替代服务。 + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. 未实现。 + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hant.xlf index 35e49716c2b..e5865835c2f 100644 --- a/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms.Design/src/Resources/xlf/SR.zh-Hant.xlf @@ -2,6 +2,26 @@ + + Copy and move {0} + Copy and move {0} + + + + Copy and move {0} Controls + Copy and move {0} Controls + + + + Move {0} + Move {0} + + + + Move {0} Controls + Move {0} Controls + + The serialization store is closed. New objects cannot be added to a closed store. 序列化存放區已關閉。新物件無法加入已關閉的存放區。 @@ -37,6 +57,106 @@ Integer 無法轉換為 Float。 + + Format {0} components (alignment) + Format {0} components (alignment) + + + + Align {0} components to grid + Align {0} components to grid + + + + Cut {0} Components + Cut {0} Components + + + + Delete {0} components + Delete {0} components + + + + An error occurred while processing this command.\r\n{0} + An error occurred while processing this command.\r\n{0} + + + + Format {0} components (spacing) + Format {0} components (spacing) + + + + Paste components + Paste components + + + + Size {0} components + Size {0} components + + + + Size {0} components to grid + Size {0} components to grid + + + + Unknown spacing command + Unknown spacing command + + + + Could not convert value '{0}' to the type '{1}'. + Could not convert value '{0}' to the type '{1}'. + + + + Could not find method '{0}'. + Could not find method '{0}'. + + + + Could not find property '{0}' on '{1}'. + Could not find property '{0}' on '{1}'. + + + + {0} Tasks + {0} Tasks + + + + Error using the dropdown: {0} + Error using the dropdown: {0} + + + + Error invoking '{0}'. Details: {1} + Error invoking '{0}'. Details: {1} + + + + Error setting value '{0}' to property '{1}'. Details: {2} + Error setting value '{0}' to property '{1}'. Details: {2} + + + + Call to BeginDrag must succeed before calling drag functions. + Call to BeginDrag must succeed before calling drag functions. + + + + Inherited control + Inherited control + + + + Inherited control (Private) + Inherited control (Private) + + The container cannot be disposed at design time. 這個容器無法在設計階段處置。 @@ -67,11 +187,6 @@ 無法從服務容器移除服務 {0}。 - - Could not find method '{0}' - 找不到方法 '{0}' - - Cannot remove or destroy inherited component '{0}'. 無法移除或摧毀繼承的元件 '{0}'。 @@ -227,6 +342,21 @@ .NET 元件 + + Drag {0} components + Drag {0} components + + + + Move {0} + Move {0} + + + + Move {0} components + Move {0} components + + This IDataObject doesn't support SetData. 此 IDataObject 不支援 SetData。 @@ -242,6 +372,16 @@ 唯讀 + + Size {0} + Size {0} + + + + Size {0} components + Size {0} components + + This method/object is not implemented by design. 此方法/物件並非由設計實作。 @@ -252,6 +392,11 @@ 此平台不支援 WinForms Designer。 + + RTL_False + RTL_False + RTL_False in Left-to-right languages and anything else (RTL_True) for right to left. + You cannot create a new session because this serialization manager already has an active serialization session. 無法建立新工作階段,因為這個序列化管理員已經有使用中的序列化工作階段。 @@ -427,11 +572,46 @@ 找不到必要的服務 {0}。如果您已移除這項服務,請務必提供替代的服務。 + + Auto Arrange Tray Icons + Auto Arrange Tray Icons + + + + Line Up Tray Icons + Line Up Tray Icons + + + + Show Large or Small Icons + Show Large or Small Icons + + + + Error + Error + + Not implemented. 未實作。 + + <couldn't find resource string "WindowsFormsAddEvent"> + <couldn't find resource string "WindowsFormsAddEvent"> + + + + Horizontal center of {0} component(s) + Horizontal center of {0} component(s) + + + + Vertical center of {0} component(s) + Vertical center of {0} component(s) + + \ No newline at end of file diff --git a/src/System.Windows.Forms.Design/src/System.Windows.Forms.Design.csproj b/src/System.Windows.Forms.Design/src/System.Windows.Forms.Design.csproj index fa03fd09df1..67c75436f47 100644 --- a/src/System.Windows.Forms.Design/src/System.Windows.Forms.Design.csproj +++ b/src/System.Windows.Forms.Design/src/System.Windows.Forms.Design.csproj @@ -10,7 +10,7 @@ true $(NoWarn);618 - $(DefineConstants);DRAWING_DESIGN_NAMESPACE; + $(DefineConstants);DRAWING_DESIGN_NAMESPACE;WINDOWS_FORMS_SWITCHES; @@ -30,6 +30,7 @@ + @@ -38,6 +39,7 @@ + diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/CodeMarkers.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/CodeMarkers.cs index 60c25b71b87..67a83d0662f 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/CodeMarkers.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/CodeMarkers.cs @@ -175,7 +175,7 @@ public bool CodeMarkerEx(int nTimerID, byte[] aBuff) // Check the arguments only after checking whether code markers are enabled // This allows the calling code to pass null value and avoid calculation of data if nothing is to be logged if (aBuff == null) - throw new ArgumentNullException("aBuff"); + throw new ArgumentNullException(nameof(aBuff)); try { @@ -236,7 +236,7 @@ public bool CodeMarkerEx(int nTimerID, string stringData) // Check the arguments only after checking whether code markers are enabled // This allows the calling code to pass null value and avoid calculation of data if nothing is to be logged if (stringData == null) - throw new ArgumentNullException("stringData"); + throw new ArgumentNullException(nameof(stringData)); try { @@ -328,7 +328,7 @@ private static bool UsePrivateCodeMarkers(string regRoot, RegistryView registryV { if (regRoot == null) { - throw new ArgumentNullException("regRoot"); + throw new ArgumentNullException(nameof(regRoot)); } // Reads the Performance subkey from the given registry key @@ -393,7 +393,7 @@ public bool InitPerformanceDll(int iApp, string strRegRoot, RegistryView registr if (strRegRoot == null) { - throw new ArgumentNullException("strRegRoot"); + throw new ArgumentNullException(nameof(strRegRoot"); } this.regroot = strRegRoot; diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ComponentDesigner.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ComponentDesigner.cs index 25c4ef3abfe..ccbc6717169 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ComponentDesigner.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ComponentDesigner.cs @@ -880,7 +880,7 @@ public object this[string propertyName] { if (propertyName == null) { - throw new ArgumentNullException("propertyName"); + throw new ArgumentNullException(nameof(propertyName)); } // First, check to see if the name is in the given properties table @@ -940,7 +940,7 @@ internal bool ShouldSerializeValue(string propertyName, object defaultValue) { if (propertyName == null) { - throw new ArgumentNullException("propertyName"); + throw new ArgumentNullException(nameof(propertyName)); } if (Contains(propertyName)) diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/DesignerHost.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/DesignerHost.cs index aa4897ea3f0..03bc5877338 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/DesignerHost.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/DesignerHost.cs @@ -174,7 +174,7 @@ internal bool AddToContainerPreProcess(IComponent component, string name, IConta { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } // We should never add anything while we're unloading. @@ -482,7 +482,7 @@ protected override object GetService(Type service) object serviceInstance = null; if (service == null) { - throw new ArgumentNullException("service"); + throw new ArgumentNullException(nameof(service)); } if (service == typeof(IMultitargetHelperService)) @@ -620,7 +620,7 @@ internal bool RemoveFromContainerPreProcess(IComponent component, IContainer con { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } ISite site = component.Site; @@ -1110,7 +1110,7 @@ IComponent IDesignerHost.CreateComponent(Type componentType, string name) { if (componentType == null) { - throw new ArgumentNullException("componentType"); + throw new ArgumentNullException(nameof(componentType)); } IComponent component; @@ -1191,7 +1191,7 @@ void IDesignerHost.DestroyComponent(IComponent component) string name; if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } if (component.Site != null && component.Site.Name != null) @@ -1238,7 +1238,7 @@ IDesigner IDesignerHost.GetDesigner(IComponent component) { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } return _designers[component] as IDesigner; } @@ -1250,7 +1250,7 @@ Type IDesignerHost.GetType(string typeName) { if (typeName == null) { - throw new ArgumentNullException("typeName"); + throw new ArgumentNullException(nameof(typeName)); } if (GetService(typeof(ITypeResolutionService)) is ITypeResolutionService ts) @@ -1841,7 +1841,7 @@ object IServiceProvider.GetService(Type service) { if (service == null) { - throw new ArgumentNullException("service"); + throw new ArgumentNullException(nameof(service)); } // We always resolve IDictionaryService to ourselves. @@ -2023,7 +2023,7 @@ protected override ISite CreateSite(IComponent component, string name) { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } return new NestedSite(component, _host, name, this); } diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ExtenderProviderService.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ExtenderProviderService.cs index 78d7ae7f412..3cc6a6e408c 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ExtenderProviderService.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/ExtenderProviderService.cs @@ -41,7 +41,7 @@ void IExtenderProviderService.AddExtenderProvider(IExtenderProvider provider) { if (provider == null) { - throw new ArgumentNullException("provider"); + throw new ArgumentNullException(nameof(provider)); } if (_providers == null) @@ -64,7 +64,7 @@ void IExtenderProviderService.RemoveExtenderProvider(IExtenderProvider provider) { if (provider == null) { - throw new ArgumentNullException("provider"); + throw new ArgumentNullException(nameof(provider)); } if (_providers != null) diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/SelectionService.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/SelectionService.cs index 7860cbc1f09..a74ba881d70 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/SelectionService.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/SelectionService.cs @@ -267,7 +267,7 @@ void IDisposable.Dispose() { if (GetService(typeof(IDesignerHost)) is IDesignerHost host) { - host.TransactionOpened -= new EventHandler(this.OnTransactionOpened); + host.TransactionOpened -= new EventHandler(OnTransactionOpened); host.TransactionClosed -= new DesignerTransactionCloseEventHandler(OnTransactionClosed); if (host.InTransaction) { @@ -353,7 +353,7 @@ bool ISelectionService.GetComponentSelected(object component) { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } return (_selection != null && _selection.Contains(component)); } @@ -423,7 +423,7 @@ void ISelectionService.SetSelectedComponents(ICollection components, SelectionTy requestedPrimary = o; if (o == null) { - throw new ArgumentNullException("components"); + throw new ArgumentNullException(nameof(components)); } break; } @@ -455,7 +455,7 @@ void ISelectionService.SetSelectedComponents(ICollection components, SelectionTy foreach (object comp in components) { if (comp == null) - throw new ArgumentNullException("components"); + throw new ArgumentNullException(nameof(components)); if (object.ReferenceEquals(comp, item)) { remove = false; @@ -476,7 +476,7 @@ void ISelectionService.SetSelectedComponents(ICollection components, SelectionTy foreach (object comp in components) { if (comp == null) - throw new ArgumentNullException("components"); + throw new ArgumentNullException(nameof(components)); if (_selection != null && _selection.Contains(comp)) { diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializer.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializer.cs index c995e73f452..3bbdbd58c9f 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializer.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializer.cs @@ -250,9 +250,9 @@ public virtual object SerializeAbsolute(IDesignerSerializationManager manager, o /// public virtual CodeStatementCollection SerializeMember(IDesignerSerializationManager manager, object owningObject, MemberDescriptor member) { - if (manager == null) throw new ArgumentNullException("manager"); - if (owningObject == null) throw new ArgumentNullException("owningObject"); - if (member == null) throw new ArgumentNullException("member"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (owningObject == null) throw new ArgumentNullException(nameof(owningObject)); + if (member == null) throw new ArgumentNullException(nameof(member)); CodeStatementCollection statements = new CodeStatementCollection(); // See if we have an existing expression for this member. If not, fabricate one @@ -287,9 +287,9 @@ public virtual CodeStatementCollection SerializeMember(IDesignerSerializationMan /// public virtual CodeStatementCollection SerializeMemberAbsolute(IDesignerSerializationManager manager, object owningObject, MemberDescriptor member) { - if (manager == null) throw new ArgumentNullException("manager"); - if (owningObject == null) throw new ArgumentNullException("owningObject"); - if (member == null) throw new ArgumentNullException("member"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (owningObject == null) throw new ArgumentNullException(nameof(owningObject)); + if (member == null) throw new ArgumentNullException(nameof(member)); CodeStatementCollection statements; SerializeAbsoluteContext abs = new SerializeAbsoluteContext(member); diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerBase.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerBase.cs index cb71e079120..da1a0e21e8c 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerBase.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerBase.cs @@ -38,8 +38,8 @@ internal CodeDomSerializerBase() /// protected virtual object DeserializeInstance(IDesignerSerializationManager manager, Type type, object[] parameters, string name, bool addToContainer) { - if (manager == null) throw new ArgumentNullException("manager"); - if (type == null) throw new ArgumentNullException("type"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (type == null) throw new ArgumentNullException(nameof(type)); return manager.CreateInstance(type, parameters, name, addToContainer); } @@ -141,8 +141,8 @@ protected static Type GetReflectionTypeFromTypeHelper(IDesignerSerializationMana private static void Error(IDesignerSerializationManager manager, string exceptionText, string helpLink) { - if (manager == null) throw new ArgumentNullException("manager"); - if (exceptionText == null) throw new ArgumentNullException("exceptionText"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (exceptionText == null) throw new ArgumentNullException(nameof(exceptionText)); CodeStatement statement = (CodeStatement)manager.Context[typeof(CodeStatement)]; CodeLinePragma linePragma = null; @@ -2031,12 +2031,12 @@ protected CodeExpression GetExpression(IDesignerSerializationManager manager, ob if (manager == null) { - throw new ArgumentNullException("manager"); + throw new ArgumentNullException(nameof(manager)); } if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } Trace("GetExpression called for object {0}", value.ToString()); @@ -2151,7 +2151,7 @@ protected CodeDomSerializer GetSerializer(IDesignerSerializationManager manager, { if (manager == null) { - throw new ArgumentNullException("manager"); + throw new ArgumentNullException(nameof(manager)); } if (value != null) { @@ -2263,8 +2263,8 @@ protected bool IsSerialized(IDesignerSerializationManager manager, object value) protected bool IsSerialized(IDesignerSerializationManager manager, object value, bool honorPreset) { bool hasExpression = false; - if (manager == null) throw new ArgumentNullException("manager"); - if (value == null) throw new ArgumentNullException("value"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (value == null) throw new ArgumentNullException(nameof(value)); // Is the expression part of a prior SetExpression call? if (manager.Context[typeof(ExpressionTable)] is ExpressionTable table && table.GetExpression(value) != null && (!honorPreset || !table.ContainsPresetExpression(value))) @@ -2284,8 +2284,8 @@ protected bool IsSerialized(IDesignerSerializationManager manager, object value, protected CodeExpression SerializeCreationExpression(IDesignerSerializationManager manager, object value, out bool isComplete) { isComplete = false; - if (manager == null) throw new ArgumentNullException("manager"); - if (value == null) throw new ArgumentNullException("value"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (value == null) throw new ArgumentNullException(nameof(value)); TypeConverter converter = TypeDescriptor.GetConverter(value); // See if there is an ExpressionContext with a preset value we're interested in. If so, that will dictate our creation expression. @@ -2465,8 +2465,8 @@ private CodeExpression SerializeInstanceDescriptor(IDesignerSerializationManager /// protected string GetUniqueName(IDesignerSerializationManager manager, object value) { - if (manager == null) throw new ArgumentNullException("manager"); - if (value == null) throw new ArgumentNullException("value"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (value == null) throw new ArgumentNullException(nameof(value)); string name = manager.GetName(value); if (name == null) @@ -2510,10 +2510,10 @@ protected string GetUniqueName(IDesignerSerializationManager manager, object val /// protected void SerializeEvent(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, EventDescriptor descriptor) { - if (manager == null) throw new ArgumentNullException("manager"); - if (statements == null) throw new ArgumentNullException("statements"); - if (value == null) throw new ArgumentNullException("value"); - if (descriptor == null) throw new ArgumentNullException("descriptor"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (statements == null) throw new ArgumentNullException(nameof(statements)); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); using (TraceScope("CodeDomSerializerBase::SerializeEvent")) { @@ -2675,10 +2675,10 @@ protected void SerializePropertiesToResources(IDesignerSerializationManager mana /// protected void SerializeProperty(IDesignerSerializationManager manager, CodeStatementCollection statements, object value, PropertyDescriptor propertyToSerialize) { - if (manager == null) throw new ArgumentNullException("manager"); - if (value == null) throw new ArgumentNullException("value"); - if (propertyToSerialize == null) throw new ArgumentNullException("propertyToSerialize"); - if (statements == null) throw new ArgumentNullException("statements"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (propertyToSerialize == null) throw new ArgumentNullException(nameof(propertyToSerialize)); + if (statements == null) throw new ArgumentNullException(nameof(statements)); Trace("CodeDomSerializerBase::SerializeProperty {0}", propertyToSerialize.Name); // Now look for a MemberCodeDomSerializer for the property. If we can't find one, then we can't serialize the property @@ -3018,9 +3018,9 @@ protected void SetExpression(IDesignerSerializationManager manager, object value [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] protected void SetExpression(IDesignerSerializationManager manager, object value, CodeExpression expression, bool isPreset) { - if (manager == null) throw new ArgumentNullException("manager"); - if (value == null) throw new ArgumentNullException("value"); - if (expression == null) throw new ArgumentNullException("expression"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); ExpressionTable table = (ExpressionTable)manager.Context[typeof(ExpressionTable)]; if (table == null) diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerException.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerException.cs index 0f93edcecaa..c714c72095e 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerException.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CodeDomSerializerException.cs @@ -69,14 +69,14 @@ public CodeLinePragma LinePragma private void FillLinePragmaFromContext(IDesignerSerializationManager manager) { if (manager == null) - throw new ArgumentNullException("manager"); + throw new ArgumentNullException(nameof(manager)); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) { - throw new ArgumentNullException("info"); + throw new ArgumentNullException(nameof(info)); } info.AddValue("linePragma", _linePragma); base.GetObjectData(info, context); diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CollectionCodeDomSerializer.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CollectionCodeDomSerializer.cs index f530fc8ffa0..b5631bf5e34 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CollectionCodeDomSerializer.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/CollectionCodeDomSerializer.cs @@ -119,7 +119,7 @@ protected bool MethodSupportsSerialization(MethodInfo method) { if (method == null) { - throw new ArgumentNullException("method"); + throw new ArgumentNullException(nameof(method)); } object[] attrs = method.GetCustomAttributes(typeof(DesignerSerializationVisibilityAttribute), true); @@ -143,12 +143,12 @@ public override object Serialize(IDesignerSerializationManager manager, object v { if (manager == null) { - throw new ArgumentNullException("manager"); + throw new ArgumentNullException(nameof(manager)); } if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } object result = null; using (TraceScope("CollectionCodeDomSerializer::Serialize")) @@ -305,10 +305,10 @@ private static MethodInfo ChooseMethodByType(TypeDescriptionProvider provider, L /// protected virtual object SerializeCollection(IDesignerSerializationManager manager, CodeExpression targetExpression, Type targetType, ICollection originalCollection, ICollection valuesToSerialize) { - if (manager == null) throw new ArgumentNullException("manager"); - if (targetType == null) throw new ArgumentNullException("targetType"); - if (originalCollection == null) throw new ArgumentNullException("originalCollection"); - if (valuesToSerialize == null) throw new ArgumentNullException("valuesToSerialize"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (targetType == null) throw new ArgumentNullException(nameof(targetType)); + if (originalCollection == null) throw new ArgumentNullException(nameof(originalCollection)); + if (valuesToSerialize == null) throw new ArgumentNullException(nameof(valuesToSerialize)); object result = null; bool serialized = false; diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ComponentCache.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ComponentCache.cs index 254d746654a..976393825f4 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ComponentCache.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ComponentCache.cs @@ -59,7 +59,7 @@ internal Entry this[object component] { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } if (_cache != null && _cache.TryGetValue(component, out Entry result)) @@ -160,7 +160,7 @@ private void OnComponentChanging(object source, ComponentChangingEventArgs ce) } else { - // Hmm. We were notified about an object change, but were unable to relate it back to a component we know about. In this situation, we have no option but to clear the whole cache, since we don't want serialization to miss something. See VSWhidbey #404813 for an example of what we would have missed. + // Hmm. We were notified about an object change, but were unable to relate it back to a component we know about. In this situation, we have no option but to clear the whole cache, since we don't want serialization to miss something. _cache.Clear(); } } @@ -191,7 +191,7 @@ private void OnComponentChanged(object source, ComponentChangedEventArgs ce) } else { - // Hmm. We were notified about an object change, but were unable to relate it back to a component we know about. In this situation, we have no option but to clear the whole cache, since we don't want serialization to miss something. See VSWhidbey #404813 for an example of what we would have missed. + // Hmm. We were notified about an object change, but were unable to relate it back to a component we know about. In this situation, we have no option but to clear the whole cache, since we don't want serialization to miss something. _cache.Clear(); } } diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/DesignerSerializationManager.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/DesignerSerializationManager.cs index e53cfc2c609..7fbb97335c8 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/DesignerSerializationManager.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/DesignerSerializationManager.cs @@ -51,7 +51,7 @@ public DesignerSerializationManager() /// public DesignerSerializationManager(IServiceProvider provider) { - this.provider = provider ?? throw new ArgumentNullException("provider"); + this.provider = provider ?? throw new ArgumentNullException(nameof(provider)); preserveNames = true; validateRecycledTypes = true; } @@ -412,7 +412,7 @@ public object GetSerializer(Type objectType, Type serializerType) { if (serializerType == null) { - throw new ArgumentNullException("serializerType"); + throw new ArgumentNullException(nameof(serializerType)); } object serializer = null; @@ -624,7 +624,7 @@ private PropertyDescriptor WrapProperty(PropertyDescriptor property, object owne { if (property == null) { - throw new ArgumentNullException("property"); + throw new ArgumentNullException(nameof(property)); } // owner can be null for static properties. return new WrappedPropertyDescriptor(property, owner); @@ -766,7 +766,7 @@ object IDesignerSerializationManager.GetInstance(string name) object instance = null; if (name == null) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } CheckSession(); @@ -798,7 +798,7 @@ string IDesignerSerializationManager.GetName(object value) string name = null; if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } CheckSession(); @@ -891,8 +891,8 @@ internal ArrayList SerializationProviders void IDesignerSerializationManager.SetName(object instance, string name) { CheckSession(); - if (instance == null) throw new ArgumentNullException("instance"); - if (name == null) throw new ArgumentNullException("name"); + if (instance == null) throw new ArgumentNullException(nameof(instance)); + if (name == null) throw new ArgumentNullException(nameof(name)); if (instancesByName == null) { diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ExpressionContext.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ExpressionContext.cs index 9688d0684b4..37118a94e1d 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ExpressionContext.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ExpressionContext.cs @@ -28,9 +28,9 @@ public ExpressionContext(CodeExpression expression, Type expressionType, object { // To make this public, we cannot have random special cases for what the args mean. Debug.Assert(expression != null && expressionType != null && owner != null, "Obsolete use of expression context."); - _expression = expression ?? throw new ArgumentNullException("expression"); - _expressionType = expressionType ?? throw new ArgumentNullException("expressionType"); - _owner = owner ?? throw new ArgumentNullException("owner"); + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + _expressionType = expressionType ?? throw new ArgumentNullException(nameof(expressionType)); + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _presetValue = presetValue; } diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ResourceCodeDomSerializer.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ResourceCodeDomSerializer.cs index 7ef5a79c6dd..488dbb3d7a8 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ResourceCodeDomSerializer.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/ResourceCodeDomSerializer.cs @@ -151,8 +151,8 @@ private SerializationResourceManager CreateResourceManager(IDesignerSerializatio /// protected override object DeserializeInstance(IDesignerSerializationManager manager, Type type, object[] parameters, string name, bool addToContainer) { - if (manager == null) throw new ArgumentNullException("manager"); - if (type == null) throw new ArgumentNullException("type"); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (type == null) throw new ArgumentNullException(nameof(type)); if (name != null && name.Equals(ResourceManagerName) && typeof(ResourceManager).IsAssignableFrom(type)) { @@ -953,7 +953,7 @@ public override ResourceSet GetResourceSet(CultureInfo culture, bool createIfNot { if (culture == null) { - throw new ArgumentNullException("culture"); + throw new ArgumentNullException(nameof(culture)); } CultureInfo lastCulture; diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/RootContext.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/RootContext.cs index 9017e1854ea..b5bae9ae838 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/RootContext.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/RootContext.cs @@ -18,8 +18,8 @@ public sealed class RootContext /// public RootContext(CodeExpression expression, object value) { - _expression = expression ?? throw new ArgumentNullException("expression"); - _value = value ?? throw new ArgumentNullException("value"); + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + _value = value ?? throw new ArgumentNullException(nameof(value)); } /// diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/StatementContext.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/StatementContext.cs index f8a658e7273..cfdd12703a6 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/StatementContext.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/Serialization/StatementContext.cs @@ -91,7 +91,7 @@ public CodeStatementCollection this[object statementOwner] { if (statementOwner == null) { - throw new ArgumentNullException("statementOwner"); + throw new ArgumentNullException(nameof(statementOwner)); } if (_table != null) @@ -126,7 +126,7 @@ public bool ContainsKey(object statementOwner) { if (statementOwner == null) { - throw new ArgumentNullException("statementOwner"); + throw new ArgumentNullException(nameof(statementOwner)); } if (_table != null) @@ -151,7 +151,7 @@ public void Populate(ICollection statementOwners) { if (statementOwners == null) { - throw new ArgumentNullException("statementOwners"); + throw new ArgumentNullException(nameof(statementOwners)); } foreach (object o in statementOwners) { @@ -166,7 +166,7 @@ public void Populate(object owner) { if (owner == null) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } AddOwner(owner, null); } diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/TypeDescriptorFilterService.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/TypeDescriptorFilterService.cs index 6487df95894..ec9d8ee2629 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/TypeDescriptorFilterService.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/TypeDescriptorFilterService.cs @@ -41,11 +41,11 @@ bool ITypeDescriptorFilterService.FilterAttributes(IComponent component, IDictio { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } if (attributes == null) { - throw new ArgumentNullException("attributes"); + throw new ArgumentNullException(nameof(attributes)); } IDesigner designer = GetDesigner(component); @@ -65,11 +65,11 @@ bool ITypeDescriptorFilterService.FilterEvents(IComponent component, IDictionary { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } if (events == null) { - throw new ArgumentNullException("events"); + throw new ArgumentNullException(nameof(events)); } IDesigner designer = GetDesigner(component); @@ -89,11 +89,11 @@ bool ITypeDescriptorFilterService.FilterProperties(IComponent component, IDictio { if (component == null) { - throw new ArgumentNullException("component"); + throw new ArgumentNullException(nameof(component)); } if (properties == null) { - throw new ArgumentNullException("properties"); + throw new ArgumentNullException(nameof(properties)); } IDesigner designer = GetDesigner(component); diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/AdornmentType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/AdornmentType.cs new file mode 100644 index 00000000000..c870a5b170d --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/AdornmentType.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + /// + /// Specifies numeric IDs for different types of adornments on a component. + /// + internal enum AdornmentType + { + /// + /// Specifies the type as grab handle adornments. + /// + GrabHandle = 1, + + /// + /// Specifies the type as container selector adornments. + /// + ContainerSelector = 2, + + /// + /// Specifies the type as the maximum size of any adornment. + /// + Maximum = 3, + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/Adorner.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/Adorner.cs index 5f8bba74855..01af9af4601 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/Adorner.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/Adorner.cs @@ -14,12 +14,16 @@ namespace System.Windows.Forms.Design.Behavior /// public sealed class Adorner { + private BehaviorService _behaviorService; //ptr back to the BehaviorService + private readonly GlyphCollection _glyphs; //collection of Glyphs that this particular Adorner manages + /// /// Standard constructor. Creates a new GlyphCollection and by default is enabled. /// public Adorner() { - throw new NotImplementedException(SR.NotImplementedByDesign); + _glyphs = new GlyphCollection(); + EnabledInternal = true; } /// @@ -28,8 +32,8 @@ public Adorner() /// public BehaviorService BehaviorService { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _behaviorService; + set => _behaviorService = value; } /// @@ -38,22 +42,36 @@ public BehaviorService BehaviorService /// public bool Enabled { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => EnabledInternal; + set + { + if (value != EnabledInternal) + { + EnabledInternal = value; + Invalidate(); + } + } } + internal bool EnabledInternal { get; set; } + /// /// Returns the stronly-typed Glyph collection. /// - public GlyphCollection Glyphs => throw new NotImplementedException(SR.NotImplementedByDesign); + public GlyphCollection Glyphs + { + get => _glyphs; + } - /// /// /// /// Forces the BehaviorService to refresh its AdornerWindow. /// public void Invalidate() { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_behaviorService != null) + { + _behaviorService.Invalidate(); + } } /// @@ -61,7 +79,7 @@ public void Invalidate() /// public void Invalidate(Rectangle rectangle) { - throw new NotImplementedException(SR.NotImplementedByDesign); + _behaviorService?.Invalidate(rectangle); } /// @@ -69,7 +87,10 @@ public void Invalidate(Rectangle rectangle) /// public void Invalidate(Region region) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_behaviorService != null) + { + _behaviorService.Invalidate(region); + } } } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/BehaviorService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/BehaviorService.cs index 3dea94441df..87ae06fa40d 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/BehaviorService.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/BehaviorService.cs @@ -2,8 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; +using System.Drawing.Design; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; +using Microsoft.Win32; namespace System.Windows.Forms.Design.Behavior { @@ -23,30 +33,188 @@ namespace System.Windows.Forms.Design.Behavior /// public sealed class BehaviorService : IDisposable { + private readonly IServiceProvider _serviceProvider; //standard service provider + private readonly AdornerWindow _adornerWindow; //the transparent window all glyphs are drawn to + private readonly BehaviorServiceAdornerCollection _adorners; //we manage all adorners (glyph-containers) here + private readonly ArrayList _behaviorStack; //the stack behavior objects can be pushed to and popped from + private Behavior _captureBehavior; //the behavior that currently has capture; may be null + private Glyph _hitTestedGlyph; //the last valid glyph that was hit tested + private IToolboxService _toolboxSvc; //allows us to have the toolbox choose a cursor + private DragEventArgs _validDragArgs; //if valid - this is used to fabricate drag enter/leave envents + private BehaviorDragDropEventHandler _beginDragHandler; //fired directly before we call .DoDragDrop() + private BehaviorDragDropEventHandler _endDragHandler; //fired directly after we call .DoDragDrop() + private EventHandler _synchronizeEventHandler; //fired when we want to synchronize the selection + private NativeMethods.TRACKMOUSEEVENT _trackMouseEvent; //demand created (once) used to track the mouse hover event + private bool _trackingMouseEvent; //state identifying current mouse tracking + private string[] _testHook_RecentSnapLines; //we keep track of the last snaplines we found - for testing purposes + private readonly MenuCommandHandler _menuCommandHandler; //private object that handles all menu commands + private bool _useSnapLines; //indicates if this designer session is using snaplines or snapping to a grid + private bool _queriedSnapLines; //only query for this once since we require the user restart design sessions when this changes + private readonly Hashtable _dragEnterReplies; // we keep track of whether glyph has already responded to a DragEnter this D&D. + private static readonly TraceSwitch s_dragDropSwitch = new TraceSwitch("BSDRAGDROP", "Behavior service drag & drop messages"); + + private bool _cancelDrag = false; // should we cancel the drag on the next QueryContinueDrag + + private int _adornerWindowIndex = -1; + + //test hooks for SnapLines + private static int WM_GETALLSNAPLINES; + private static int WM_GETRECENTSNAPLINES; + + private DesignerActionUI _actionPointer; // pointer to the designer action service so we can supply mouse over notifications + + private const string ToolboxFormat = ".NET Toolbox Item"; // used to detect if a drag is coming from the toolbox. [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] internal BehaviorService(IServiceProvider serviceProvider, Control windowFrame) { - throw new NotImplementedException(SR.NotImplementedByDesign); + _serviceProvider = serviceProvider; + //create the AdornerWindow + _adornerWindow = new AdornerWindow(this, windowFrame); + + //use the adornerWindow as an overlay + IOverlayService os = (IOverlayService)serviceProvider.GetService(typeof(IOverlayService)); + if (os != null) + { + _adornerWindowIndex = os.PushOverlay(_adornerWindow); + } + + _dragEnterReplies = new Hashtable(); + + //start with an empty adorner collection & no behavior on the stack + _adorners = new BehaviorServiceAdornerCollection(this); + _behaviorStack = new ArrayList(); + + _hitTestedGlyph = null; + _validDragArgs = null; + _actionPointer = null; + _trackMouseEvent = null; + _trackingMouseEvent = false; + + //create out object that will handle all menucommands + if (serviceProvider.GetService(typeof(IMenuCommandService)) is IMenuCommandService menuCommandService && serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host) + { + _menuCommandHandler = new MenuCommandHandler(this, menuCommandService); + host.RemoveService(typeof(IMenuCommandService)); + host.AddService(typeof(IMenuCommandService), _menuCommandHandler); + } + + //default layoutmode is SnapToGrid. + _useSnapLines = false; + _queriedSnapLines = false; + + //test hooks + WM_GETALLSNAPLINES = SafeNativeMethods.RegisterWindowMessage("WM_GETALLSNAPLINES"); + WM_GETRECENTSNAPLINES = SafeNativeMethods.RegisterWindowMessage("WM_GETRECENTSNAPLINES"); } /// /// Read-only property that returns the AdornerCollection that the BehaivorService manages. /// - public BehaviorServiceAdornerCollection Adorners => - throw new NotImplementedException(SR.NotImplementedByDesign); + public BehaviorServiceAdornerCollection Adorners + { + get => _adorners; + } + + /// + /// Returns the actual Control that represents the transparent AdornerWindow. + /// + internal Control AdornerWindowControl + { + get => _adornerWindow; + } + + internal bool HasCapture + { + get => _captureBehavior != null; + } + + /// + /// Returns the LayoutMode setting of the current designer session. Either SnapLines or SnapToGrid. + /// + internal bool UseSnapLines + { + get + { + //we only check for this service/value once since we require the user to re-open the designer session after these types of option have been modified + if (!_queriedSnapLines) + { + _queriedSnapLines = true; + _useSnapLines = DesignerUtils.UseSnapLines(_serviceProvider); + } + + return _useSnapLines; + } + } /// /// Creates and returns a Graphics object for the AdornerWindow /// - public Graphics AdornerWindowGraphics => throw new NotImplementedException(SR.NotImplementedByDesign); + public Graphics AdornerWindowGraphics + { + get + { + Graphics result = _adornerWindow.CreateGraphics(); + result.Clip = new Region(_adornerWindow.DesignerFrameDisplayRectangle); + return result; + } + } - public Behavior CurrentBehavior => throw new NotImplementedException(SR.NotImplementedByDesign); + public Behavior CurrentBehavior + { + get + { + if (_behaviorStack != null && _behaviorStack.Count > 0) + { + return (_behaviorStack[0] as Behavior); + } + else + { + return null; + } + } + } + + internal bool CancelDrag + { + get => _cancelDrag; + set => _cancelDrag = value; + } + internal DesignerActionUI DesignerActionUI + { + get => _actionPointer; + set => _actionPointer = value; + } + /// + /// Called by the DragAssistanceManager after a snapline/drag op has completed - we store this data for testing purposes. See TestHook_GetRecentSnapLines method. + /// + internal string[] RecentSnapLines + { + set => _testHook_RecentSnapLines = value; + } /// /// Disposes the behavior service. /// public void Dispose() { - throw new NotImplementedException(SR.NotImplementedByDesign); + // remove adorner window from overlay service + IOverlayService os = (IOverlayService)_serviceProvider.GetService(typeof(IOverlayService)); + if (os != null) + { + os.RemoveOverlay(_adornerWindow); + } + + MenuCommandHandler menuCommandHandler = null; + if (_serviceProvider.GetService(typeof(IMenuCommandService)) is IMenuCommandService menuCommandService) + menuCommandHandler = menuCommandService as MenuCommandHandler; + + if (menuCommandHandler != null && _serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host) + { + IMenuCommandService oldMenuCommandService = menuCommandHandler.MenuService; + host.RemoveService(typeof(IMenuCommandService)); + host.AddService(typeof(IMenuCommandService), oldMenuCommandService); + } + + _adornerWindow.Dispose(); } /// @@ -54,7 +222,9 @@ public void Dispose() /// public Point AdornerWindowPointToScreen(Point p) { - throw new NotImplementedException(SR.NotImplementedByDesign); + NativeMethods.POINT offset = new NativeMethods.POINT(p.X, p.Y); + NativeMethods.MapWindowPoints(_adornerWindow.Handle, IntPtr.Zero, offset, 1); + return new Point(offset.x, offset.y); } /// @@ -62,7 +232,8 @@ public Point AdornerWindowPointToScreen(Point p) /// public Point AdornerWindowToScreen() { - throw new NotImplementedException(SR.NotImplementedByDesign); + Point origin = new Point(0, 0); + return AdornerWindowPointToScreen(origin); } /// @@ -70,7 +241,20 @@ public Point AdornerWindowToScreen() /// public Point ControlToAdornerWindow(Control c) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (c.Parent == null) + { + return Point.Empty; + } + + NativeMethods.POINT pt = new NativeMethods.POINT(); + pt.x = c.Left; + pt.y = c.Top; + NativeMethods.MapWindowPoints(c.Parent.Handle, _adornerWindow.Handle, pt, 1); + if (c.Parent.IsMirrored) + { + pt.x -= c.Width; + } + return new Point(pt.x, pt.y); } /// @@ -78,7 +262,11 @@ public Point ControlToAdornerWindow(Control c) /// public Point MapAdornerWindowPoint(IntPtr handle, Point pt) { - throw new NotImplementedException(SR.NotImplementedByDesign); + NativeMethods.POINT nativePoint = new NativeMethods.POINT(); + nativePoint.x = pt.X; + nativePoint.y = pt.Y; + NativeMethods.MapWindowPoints(handle, _adornerWindow.Handle, nativePoint, 1); + return new Point(nativePoint.x, nativePoint.y); } /// @@ -86,7 +274,13 @@ public Point MapAdornerWindowPoint(IntPtr handle, Point pt) /// public Rectangle ControlRectInAdornerWindow(Control c) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (c.Parent == null) + { + return Rectangle.Empty; + } + Point loc = ControlToAdornerWindow(c); + + return new Rectangle(loc, c.Size); } /// @@ -95,8 +289,14 @@ public Rectangle ControlRectInAdornerWindow(Control c) /// public event BehaviorDragDropEventHandler BeginDrag { - add => throw new NotImplementedException(SR.NotImplementedByDesign); - remove => throw new NotImplementedException(SR.NotImplementedByDesign); + add + { + _beginDragHandler += value; + } + remove + { + _beginDragHandler -= value; + } } /// @@ -105,8 +305,14 @@ public event BehaviorDragDropEventHandler BeginDrag /// public event BehaviorDragDropEventHandler EndDrag { - add => throw new NotImplementedException(SR.NotImplementedByDesign); - remove => throw new NotImplementedException(SR.NotImplementedByDesign); + add + { + _endDragHandler += value; + } + remove + { + _endDragHandler -= value; + } } /// @@ -114,9 +320,15 @@ public event BehaviorDragDropEventHandler EndDrag /// public event EventHandler Synchronize { - add => throw new NotImplementedException(SR.NotImplementedByDesign); + add + { + _synchronizeEventHandler += value; + } - remove => throw new NotImplementedException(SR.NotImplementedByDesign); + remove + { + _synchronizeEventHandler -= value; + } } /// @@ -126,7 +338,24 @@ public event EventHandler Synchronize /// public Behavior GetNextBehavior(Behavior behavior) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_behaviorStack != null && _behaviorStack.Count > 0) + { + int index = _behaviorStack.IndexOf(behavior); + if ((index != -1) && (index < _behaviorStack.Count - 1)) + { + return _behaviorStack[index + 1] as Behavior; + } + } + return null; + } + + internal void EnableAllAdorners(bool enabled) + { + foreach (Adorner adorner in Adorners) + { + adorner.EnabledInternal = enabled; + } + Invalidate(); } /// @@ -135,7 +364,7 @@ public Behavior GetNextBehavior(Behavior behavior) /// public void Invalidate() { - throw new NotImplementedException(SR.NotImplementedByDesign); + _adornerWindow.InvalidateAdornerWindow(); } /// @@ -144,7 +373,7 @@ public void Invalidate() /// public void Invalidate(Rectangle rect) { - throw new NotImplementedException(SR.NotImplementedByDesign); + _adornerWindow.InvalidateAdornerWindow(rect); } /// @@ -153,7 +382,7 @@ public void Invalidate(Rectangle rect) /// public void Invalidate(Region r) { - throw new NotImplementedException(SR.NotImplementedByDesign); + _adornerWindow.InvalidateAdornerWindow(r); } /// @@ -161,7 +390,10 @@ public void Invalidate(Region r) /// public void SyncSelection() { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_synchronizeEventHandler != null) + { + _synchronizeEventHandler(this, EventArgs.Empty); + } } /// @@ -169,7 +401,30 @@ public void SyncSelection() /// public Behavior PopBehavior(Behavior behavior) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_behaviorStack.Count == 0) + { + throw new InvalidOperationException(); + } + + int index = _behaviorStack.IndexOf(behavior); + if (index == -1) + { + Debug.Assert(false, "Could not find the behavior to pop - did it already get popped off? " + behavior.ToString()); + return null; + } + + _behaviorStack.RemoveAt(index); + if (behavior == _captureBehavior) + { + _adornerWindow.Capture = false; + // Defensive: adornerWindow should get a WM_CAPTURECHANGED, but do this by hand if it didn't. + if (_captureBehavior != null) + { + OnLoseCapture(); + Debug.Assert(_captureBehavior == null, "OnLostCapture should have cleared captureBehavior"); + } + } + return behavior; } /// @@ -178,7 +433,18 @@ public Behavior PopBehavior(Behavior behavior) /// public void PushBehavior(Behavior behavior) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (behavior == null) + { + throw new ArgumentNullException(nameof(behavior)); + } + + // Should we catch this + _behaviorStack.Insert(0, behavior); + // If there is a capture behavior, and it isn't this behavior, notify it that it no longer has capture. + if (_captureBehavior != null && _captureBehavior != behavior) + { + OnLoseCapture(); + } } /// @@ -188,7 +454,20 @@ public void PushBehavior(Behavior behavior) /// public void PushCaptureBehavior(Behavior behavior) { - throw new NotImplementedException(SR.NotImplementedByDesign); + PushBehavior(behavior); + _captureBehavior = behavior; + _adornerWindow.Capture = true; + + // Since we are now capturing all mouse messages, we might miss some WM_MOUSEACTIVATE which would have activated the app. So if the DialogOwnerWindow (e.g. VS) is not the active window, let's activate it here. + IUIService uiService = (IUIService)_serviceProvider.GetService(typeof(IUIService)); + if (uiService != null) + { + IWin32Window hwnd = uiService.GetDialogOwnerWindow(); + if (hwnd != null && hwnd.Handle != IntPtr.Zero && hwnd.Handle != UnsafeNativeMethods.GetActiveWindow()) + { + UnsafeNativeMethods.SetActiveWindow(new HandleRef(this, hwnd.Handle)); + } + } } /// @@ -196,7 +475,1220 @@ public void PushCaptureBehavior(Behavior behavior) /// public Point ScreenToAdornerWindow(Point p) { - throw new NotImplementedException(SR.NotImplementedByDesign); + NativeMethods.POINT offset = new NativeMethods.POINT(); + offset.x = p.X; + offset.y = p.Y; + NativeMethods.MapWindowPoints(IntPtr.Zero, _adornerWindow.Handle, offset, 1); + return new Point(offset.x, offset.y); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal void OnLoseCapture() + { + if (_captureBehavior != null) + { + Behavior b = _captureBehavior; + _captureBehavior = null; + try + { + b.OnLoseCapture(_hitTestedGlyph, EventArgs.Empty); + } + catch + { + } + } + } + + /// + /// The AdornerWindow is a transparent window that resides ontop of the Designer's Frame. This window is used by the BehaviorService to intercept all messages. It also serves as a unified canvas on which to paint Glyphs. + /// + private class AdornerWindow : Control + { + private BehaviorService _behaviorService;//ptr back to BehaviorService + private Control _designerFrame;//the designer's frame + private static MouseHook s_mouseHook; // shared mouse hook + private static List s_adornerWindowList = new List(); + private bool _processingDrag; // is this particular window in a drag operation + + /// + /// Constructor that parents itself to the Designer Frame and hooks all + /// necessary events. + /// + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + internal AdornerWindow(BehaviorService behaviorService, Control designerFrame) + { + this._behaviorService = behaviorService; + this._designerFrame = designerFrame; + Dock = DockStyle.Fill; + AllowDrop = true; + Text = "AdornerWindow"; + SetStyle(ControlStyles.Opaque, true); + } + + /// + /// The key here is to set the appropriate TransparetWindow style. + /// + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.Style &= ~(NativeMethods.WS_CLIPCHILDREN | NativeMethods.WS_CLIPSIBLINGS); + cp.ExStyle |= 0x00000020/*WS_EX_TRANSPARENT*/; + return cp; + } + } + + internal bool ProcessingDrag + { + get => _processingDrag; + set => _processingDrag = value; + } + + /// + /// We'll use CreateHandle as our notification for creating our mouse hooker. + /// + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + s_adornerWindowList.Add(this); + if (s_mouseHook == null) + { + s_mouseHook = new MouseHook(); + } + } + + /// + /// Unhook and null out our mouseHook. + /// + protected override void OnHandleDestroyed(EventArgs e) + { + s_adornerWindowList.Remove(this); + // unregister the mouse hook once all adorner windows have been disposed. + if (s_adornerWindowList.Count == 0 && s_mouseHook != null) + { + s_mouseHook.Dispose(); + s_mouseHook = null; + } + base.OnHandleDestroyed(e); + } + + /// + /// Null out our mouseHook and unhook any events. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_designerFrame != null) + { + _designerFrame = null; + } + } + base.Dispose(disposing); + } + + /// + /// Returns true if the DesignerFrame is created & not being disposed. + /// + internal Control DesignerFrame + { + get => _designerFrame; + } + + /// + /// Returns the display rectangle for the adorner window + /// + internal Rectangle DesignerFrameDisplayRectangle + { + get + { + if (DesignerFrameValid) + { + return ((DesignerFrame)_designerFrame).DisplayRectangle; + } + else + { + return Rectangle.Empty; + } + } + } + + /// + /// Returns true if the DesignerFrame is created & not being disposed. + /// + internal bool DesignerFrameValid + { + get + { + if (_designerFrame == null || _designerFrame.IsDisposed || !_designerFrame.IsHandleCreated) + { + return false; + } + return true; + } + } + + public IEnumerable Adorners { get; private set; } + + /// + /// Ultimately called by ControlDesigner when it receives a DragDrop message - here, we'll exit from 'drag mode'. + /// + internal void EndDragNotification() + { + ProcessingDrag = false; + } + + /// + /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. Note the they use of the .Update() call for perf. purposes. + /// + internal void InvalidateAdornerWindow() + { + if (DesignerFrameValid) + { + _designerFrame.Invalidate(true); + _designerFrame.Update(); + } + } + + /// + /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. Note the they use of the .Update() call for perf. purposes. + /// + internal void InvalidateAdornerWindow(Region region) + { + if (DesignerFrameValid) + { + //translate for non-zero scroll positions + Point scrollPosition = ((DesignerFrame)_designerFrame).AutoScrollPosition; + region.Translate(scrollPosition.X, scrollPosition.Y); + + _designerFrame.Invalidate(region, true); + _designerFrame.Update(); + } + } + + /// + /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. Note the they use of the .Update() call for perf. purposes. + /// + internal void InvalidateAdornerWindow(Rectangle rectangle) + { + if (DesignerFrameValid) + { + //translate for non-zero scroll positions + Point scrollPosition = ((DesignerFrame)_designerFrame).AutoScrollPosition; + rectangle.Offset(scrollPosition.X, scrollPosition.Y); + + _designerFrame.Invalidate(rectangle, true); + _designerFrame.Update(); + } + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnDragDrop(DragEventArgs e) + { + try + { + _behaviorService.OnDragDrop(e); + } + finally + { + ProcessingDrag = false; + } + } + + internal void EnableAllAdorners(bool enabled) + { + foreach (Adorner adorner in Adorners) + { + adorner.EnabledInternal = enabled; + } + Invalidate(); + } + + private static bool IsLocalDrag(DragEventArgs e) + { + if (e.Data is DropSourceBehavior.BehaviorDataObject) + { + return true; + } + else + { + // Gets all the data formats and data conversion formats in the data object. + string[] allFormats = e.Data.GetFormats(); + + for (int i = 0; i < allFormats.Length; i++) + { + if (allFormats[i].Length == ToolboxFormat.Length && + string.Equals(ToolboxFormat, allFormats[i])) + { + return true; + } + } + } + return false; + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnDragEnter(DragEventArgs e) + { + ProcessingDrag = true; + + // determine if this is a local drag, if it is, do normal processing otherwise, force a PropagateHitTest. We need to force this because the OLE D&D service suspends mouse messages when the drag is not local so the mouse hook never sees them. + if (!IsLocalDrag(e)) + { + _behaviorService._validDragArgs = e; + NativeMethods.POINT pt = new NativeMethods.POINT(); + NativeMethods.GetCursorPos(pt); + NativeMethods.MapWindowPoints(IntPtr.Zero, Handle, pt, 1); + Point mousePos = new Point(pt.x, pt.y); + _behaviorService.PropagateHitTest(mousePos); + + } + _behaviorService.OnDragEnter(null, e); + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnDragLeave(EventArgs e) + { + // set our dragArgs to null so we know not to send drag enter/leave events when we re-enter the dragging area + _behaviorService._validDragArgs = null; + try + { + _behaviorService.OnDragLeave(null, e); + } + finally + { + ProcessingDrag = false; + } + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnDragOver(DragEventArgs e) + { + ProcessingDrag = true; + if (!IsLocalDrag(e)) + { + _behaviorService._validDragArgs = e; + NativeMethods.POINT pt = new NativeMethods.POINT(); + NativeMethods.GetCursorPos(pt); + NativeMethods.MapWindowPoints(IntPtr.Zero, Handle, pt, 1); + Point mousePos = new Point(pt.x, pt.y); + _behaviorService.PropagateHitTest(mousePos); + } + + _behaviorService.OnDragOver(e); + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnGiveFeedback(GiveFeedbackEventArgs e) + { + _behaviorService.OnGiveFeedback(e); + } + + /// + /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate Behavior via the BehaviorService. + /// + protected override void OnQueryContinueDrag(QueryContinueDragEventArgs e) + { + _behaviorService.OnQueryContinueDrag(e); + } + + /// + /// Called by ControlDesigner when it receives a DragEnter message - we'll let listen to all Mouse Messages so we can send drag notifcations. + /// + internal void StartDragNotification() + { + ProcessingDrag = true; + } + + /// + /// The AdornerWindow intercepts all designer-related messages and forwards them to the BehaviorService for appropriate actions. Note that Paint and HitTest messages are correctly parsed and translated to AdornerWindow coords. + /// + protected override void WndProc(ref Message m) + { + //special test hooks + if (m.Msg == BehaviorService.WM_GETALLSNAPLINES) + { + _behaviorService.TestHook_GetAllSnapLines(ref m); + } + else if (m.Msg == BehaviorService.WM_GETRECENTSNAPLINES) + { + _behaviorService.TestHook_GetRecentSnapLines(ref m); + } + + switch (m.Msg) + { + case NativeMethods.WM_PAINT: + // Stash off the region we have to update + IntPtr hrgn = NativeMethods.CreateRectRgn(0, 0, 0, 0); + NativeMethods.GetUpdateRgn(m.HWnd, hrgn, true); + // The region we have to update in terms of the smallest rectangle that completely encloses the update region of the window gives us the clip rectangle + NativeMethods.RECT clip = new NativeMethods.RECT(); + NativeMethods.GetUpdateRect(m.HWnd, ref clip, true); + Rectangle paintRect = new Rectangle(clip.left, clip.top, clip.right - clip.left, clip.bottom - clip.top); + + try + { + using (Region r = Region.FromHrgn(hrgn)) + { + // Call the base class to do its painting. + DefWndProc(ref m); + // Now do our own painting. + using (Graphics g = Graphics.FromHwnd(m.HWnd)) + { + using (PaintEventArgs pevent = new PaintEventArgs(g, paintRect)) + { + g.Clip = r; + _behaviorService.PropagatePaint(pevent); + } + } + } + } + finally + { + NativeMethods.DeleteObject(hrgn); + } + break; + + case NativeMethods.WM_NCHITTEST: + Point pt = new Point((short)NativeMethods.Util.LOWORD(unchecked((int)(long)m.LParam)), + (short)NativeMethods.Util.HIWORD(unchecked((int)(long)m.LParam))); + NativeMethods.POINT pt1 = new NativeMethods.POINT + { + x = 0, + y = 0 + }; + NativeMethods.MapWindowPoints(IntPtr.Zero, Handle, pt1, 1); + pt.Offset(pt1.x, pt1.y); + if (_behaviorService.PropagateHitTest(pt) && !ProcessingDrag) + { + m.Result = (IntPtr)(NativeMethods.HTTRANSPARENT); + } + else + { + m.Result = (IntPtr)(NativeMethods.HTCLIENT); + } + break; + + case NativeMethods.WM_CAPTURECHANGED: + base.WndProc(ref m); + _behaviorService.OnLoseCapture(); + break; + + default: + base.WndProc(ref m); + break; + } + } + + /// + /// Called by our mouseHook when it spies a mouse message that the adornerWindow would be interested in. + /// Returning 'true' signifies that the message was processed and should not continue to child windows. + /// + private bool WndProcProxy(ref Message m, int x, int y) + { + Point mouseLoc = new Point(x, y); + _behaviorService.PropagateHitTest(mouseLoc); + switch (m.Msg) + { + case NativeMethods.WM_LBUTTONDOWN: + if (_behaviorService.OnMouseDown(MouseButtons.Left, mouseLoc)) + { + return false; + } + break; + + case NativeMethods.WM_RBUTTONDOWN: + if (_behaviorService.OnMouseDown(MouseButtons.Right, mouseLoc)) + { + return false; + } + break; + + case NativeMethods.WM_MOUSEMOVE: + if (_behaviorService.OnMouseMove(Control.MouseButtons, mouseLoc)) + { + return false; + } + break; + + case NativeMethods.WM_LBUTTONUP: + if (_behaviorService.OnMouseUp(MouseButtons.Left)) + { + return false; + } + break; + + case NativeMethods.WM_RBUTTONUP: + if (_behaviorService.OnMouseUp(MouseButtons.Right)) + { + return false; + } + break; + + case NativeMethods.WM_MOUSEHOVER: + if (_behaviorService.OnMouseHover(mouseLoc)) + { + return false; + } + break; + + case NativeMethods.WM_LBUTTONDBLCLK: + if (_behaviorService.OnMouseDoubleClick(MouseButtons.Left, mouseLoc)) + { + return false; + } + break; + + case NativeMethods.WM_RBUTTONDBLCLK: + if (_behaviorService.OnMouseDoubleClick(MouseButtons.Right, mouseLoc)) + { + return false; + } + break; + } + return true; + } + + /// + /// This class knows how to hook all the messages to a given process/thread. + /// On any mouse clicks, it asks the designer what to do with the message, that is to eat it or propogate it to the control it was meant for. This allows us to synchrounously process mouse messages when the AdornerWindow itself may be pumping messages. + /// + [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] + private class MouseHook + { + private AdornerWindow _currentAdornerWindow; + private int _thisProcessID = 0; + private GCHandle _mouseHookRoot; + [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + private IntPtr _mouseHookHandle = IntPtr.Zero; + private bool _processingMessage; + + private bool _isHooked = false; + private int _lastLButtonDownTimeStamp; + + public MouseHook() + { +#if DEBUG + _callingStack = Environment.StackTrace; +#endif + HookMouse(); + } +#if DEBUG + readonly string _callingStack; + ~MouseHook() + { + Debug.Assert(_mouseHookHandle == IntPtr.Zero, "Finalizing an active mouse hook. This will crash the process. Calling stack: " + _callingStack); + } +#endif + + public void Dispose() + { + UnhookMouse(); + } + + private void HookMouse() + { + Debug.Assert(AdornerWindow.s_adornerWindowList.Count > 0, "No AdornerWindow available to create the mouse hook"); + lock (this) + { + if (_mouseHookHandle != IntPtr.Zero || AdornerWindow.s_adornerWindowList.Count == 0) + { + return; + } + + if (_thisProcessID == 0) + { + AdornerWindow adornerWindow = AdornerWindow.s_adornerWindowList[0]; + UnsafeNativeMethods.GetWindowThreadProcessId(new HandleRef(adornerWindow, adornerWindow.Handle), out _thisProcessID); + } + + NativeMethods.HookProc hook = new NativeMethods.HookProc(MouseHookProc); + _mouseHookRoot = GCHandle.Alloc(hook); + +#pragma warning disable 618 + _mouseHookHandle = UnsafeNativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE, + hook, + new HandleRef(null, IntPtr.Zero), + AppDomain.GetCurrentThreadId()); +#pragma warning restore 618 + if (_mouseHookHandle != IntPtr.Zero) + { + _isHooked = true; + } + Debug.Assert(_mouseHookHandle != IntPtr.Zero, "Failed to install mouse hook"); + } + } + + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private unsafe IntPtr MouseHookProc(int nCode, IntPtr wparam, IntPtr lparam) + { + if (_isHooked && nCode == NativeMethods.HC_ACTION) + { + NativeMethods.MOUSEHOOKSTRUCT mhs = (NativeMethods.MOUSEHOOKSTRUCT)UnsafeNativeMethods.PtrToStructure(lparam, typeof(NativeMethods.MOUSEHOOKSTRUCT)); + if (mhs != null) + { + try + { + if (ProcessMouseMessage(mhs.hWnd, unchecked((int)(long)wparam), mhs.pt_x, mhs.pt_y)) + { + return (IntPtr)1; + } + } + catch (Exception ex) + { + _currentAdornerWindow.Capture = false; + if (ex != CheckoutException.Canceled) + { + _currentAdornerWindow._behaviorService.ShowError(ex); + } + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + finally + { + _currentAdornerWindow = null; + } + } + } + + Debug.Assert(_isHooked, "How did we get here when we are diposed?"); + return UnsafeNativeMethods.CallNextHookEx(new HandleRef(this, _mouseHookHandle), nCode, wparam, lparam); + } + + private void UnhookMouse() + { + lock (this) + { + if (_mouseHookHandle != IntPtr.Zero) + { + UnsafeNativeMethods.UnhookWindowsHookEx(new HandleRef(this, _mouseHookHandle)); + _mouseHookRoot.Free(); + _mouseHookHandle = IntPtr.Zero; + _isHooked = false; + } + } + } + + private bool ProcessMouseMessage(IntPtr hWnd, int msg, int x, int y) + { + if (_processingMessage) + { + return false; + } + // We could have hooked a control in a semitrust web page. This would put semitrust frames above us, which could cause this to fail. + // SECREVIEW, UNDONE. Think hard about this. Does this allow a project to have a web page that pointed to a malicious control? + // I don't think so, because the malicious control would still be on the frame. + new NamedPermissionSet("FullTrust").Assert(); + + foreach (AdornerWindow adornerWindow in AdornerWindow.s_adornerWindowList) + { + if (!adornerWindow.DesignerFrameValid) + { + continue; + } + + _currentAdornerWindow = adornerWindow; + IntPtr handle = adornerWindow.DesignerFrame.Handle; + + // if it's us or one of our children, just process as normal + if (adornerWindow.ProcessingDrag || (hWnd != handle && SafeNativeMethods.IsChild(new HandleRef(this, handle), new HandleRef(this, hWnd)))) + { + Debug.Assert(_thisProcessID != 0, "Didn't get our process id!"); + // make sure the window is in our process + UnsafeNativeMethods.GetWindowThreadProcessId(new HandleRef(null, hWnd), out int pid); + // if this isn't our process, bail + if (pid != _thisProcessID) + { + return false; + } + + try + { + _processingMessage = true; + NativeMethods.POINT pt = new NativeMethods.POINT + { + x = x, + y = y + }; + NativeMethods.MapWindowPoints(IntPtr.Zero, adornerWindow.Handle, pt, 1); + Message m = Message.Create(hWnd, msg, (IntPtr)0, (IntPtr)MAKELONG(pt.y, pt.x)); + + // DevDiv Bugs 79616, No one knows why we get an extra click here from VS. As a workaround, we check the TimeStamp and discard it. + if (m.Msg == NativeMethods.WM_LBUTTONDOWN) + { + _lastLButtonDownTimeStamp = UnsafeNativeMethods.GetMessageTime(); + } + else if (m.Msg == NativeMethods.WM_LBUTTONDBLCLK) + { + int lButtonDoubleClickTimeStamp = UnsafeNativeMethods.GetMessageTime(); + if (lButtonDoubleClickTimeStamp == _lastLButtonDownTimeStamp) + { + return true; + } + } + + if (!adornerWindow.WndProcProxy(ref m, pt.x, pt.y)) + { + // we did the work, stop the message propogation + return true; + } + + } + finally + { + _processingMessage = false; + } + break; // no need to enumerate the other adorner windows since only one can be focused at a time. + } + } + return false; + } + + public static int MAKELONG(int low, int high) + { + return (high << 16) | (low & 0xffff); + } + } + } + + private bool PropagateHitTest(Point pt) + { + for (int i = _adorners.Count - 1; i >= 0; i--) + { + if (!_adorners[i].Enabled) + { + continue; + } + + for (int j = 0; j < _adorners[i].Glyphs.Count; j++) + { + Cursor hitTestCursor = _adorners[i].Glyphs[j].GetHitTest(pt); + if (hitTestCursor != null) + { + // InvokeMouseEnterGlyph will cause the selection to change, which might change the number of glyphs, so we need to remember the new glyph before calling InvokeMouseEnterLeave. + Glyph newGlyph = _adorners[i].Glyphs[j]; + + //with a valid hit test, fire enter/leave events + InvokeMouseEnterLeave(_hitTestedGlyph, newGlyph); + if (_validDragArgs == null) + { + //if we're not dragging, set the appropriate cursor + SetAppropriateCursor(hitTestCursor); + } + + _hitTestedGlyph = newGlyph; + //return true if we hit on a transparentBehavior, otherwise false + return (_hitTestedGlyph.Behavior is ControlDesigner.TransparentBehavior); + } + } + } + + InvokeMouseEnterLeave(_hitTestedGlyph, null); + if (_validDragArgs == null) + { + Cursor cursor = Cursors.Default; + if ((_behaviorStack != null) && (_behaviorStack.Count > 0)) + { + if (_behaviorStack[0] is Behavior behavior) + { + cursor = behavior.Cursor; + } + } + SetAppropriateCursor(cursor); + } + _hitTestedGlyph = null; + return true; // Returning false will cause the transparent window to return HTCLIENT when handling WM_NCHITTEST, thus blocking underline window to receive mouse events. + } + + private class MenuCommandHandler : IMenuCommandService + { + private readonly BehaviorService _owner; // ptr back to the behavior service + private readonly IMenuCommandService _menuService; // core service used for most implementations of the IMCS interface + private readonly Stack _currentCommands = new Stack(); + + public MenuCommandHandler(BehaviorService owner, IMenuCommandService menuService) + { + _owner = owner; + _menuService = menuService; + } + + public IMenuCommandService MenuService + { + get => _menuService; + } + + void IMenuCommandService.AddCommand(MenuCommand command) + { + _menuService.AddCommand(command); + } + + void IMenuCommandService.RemoveVerb(DesignerVerb verb) + { + _menuService.RemoveVerb(verb); + } + + void IMenuCommandService.RemoveCommand(MenuCommand command) + { + _menuService.RemoveCommand(command); + } + + MenuCommand IMenuCommandService.FindCommand(CommandID commandID) + { + try + { + if (_currentCommands.Contains(commandID)) + { + return null; + } + _currentCommands.Push(commandID); + return _owner.FindCommand(commandID, _menuService); + } + finally + { + _currentCommands.Pop(); + } + } + + bool IMenuCommandService.GlobalInvoke(CommandID commandID) + { + return _menuService.GlobalInvoke(commandID); + } + + void IMenuCommandService.ShowContextMenu(CommandID menuID, int x, int y) + { + _menuService.ShowContextMenu(menuID, x, y); + } + + void IMenuCommandService.AddVerb(DesignerVerb verb) + { + _menuService.AddVerb(verb); + } + + DesignerVerbCollection IMenuCommandService.Verbs + { + get => _menuService.Verbs; + } + } + + private MenuCommand FindCommand(CommandID commandID, IMenuCommandService menuService) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior != null) + { + //if the behavior wants all commands disabled.. + if (behavior.DisableAllCommands) + { + MenuCommand menuCommand = menuService.FindCommand(commandID); + if (menuCommand != null) + { + menuCommand.Enabled = false; + } + return menuCommand; + } + // check to see if the behavior wants to interrupt this command + else + { + MenuCommand menuCommand = behavior.FindCommand(commandID); + if (menuCommand != null) + { + // the behavior chose to interrupt - so return the new command + return menuCommand; + } + } + } + return menuService.FindCommand(commandID); + } + + private Behavior GetAppropriateBehavior(Glyph g) { + if (_behaviorStack != null && _behaviorStack.Count > 0) { + return _behaviorStack[0] as Behavior; + } + + if (g != null && g.Behavior != null) { + return g.Behavior; + } + + return null; + } + + private void ShowError(Exception ex) + { + if (_serviceProvider.GetService(typeof(IUIService)) is IUIService uis) + { + uis.ShowError(ex); + } + } + + private void SetAppropriateCursor(Cursor cursor) + { + //default cursors will let the toolbox svc set a cursor if needed + if (cursor == Cursors.Default) + { + if (_toolboxSvc == null) + { + _toolboxSvc = (IToolboxService)_serviceProvider.GetService(typeof(IToolboxService)); + } + + if (_toolboxSvc != null && _toolboxSvc.SetCursor()) + { + cursor = new Cursor(NativeMethods.GetCursor()); + } + } + _adornerWindow.Cursor = cursor; + } + + private void InvokeMouseEnterLeave(Glyph leaveGlyph, Glyph enterGlyph) + { + if (leaveGlyph != null) + { + if (enterGlyph != null && leaveGlyph.Equals(enterGlyph)) + { + //same glyph - no change + return; + } + if (_validDragArgs != null) + { + OnDragLeave(leaveGlyph, EventArgs.Empty); + } + else + { + OnMouseLeave(leaveGlyph); + } + } + + if (enterGlyph != null) + { + if (_validDragArgs != null) + { + OnDragEnter(enterGlyph, _validDragArgs); + } + else + { + OnMouseEnter(enterGlyph); + } + } + } + private void OnDragEnter(Glyph g, DragEventArgs e) + { + // if the AdornerWindow receives a drag message, this fn() will be called w/o a glyph - so we'll assign the last hit tested one + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "BS::OnDragEnter"); + if (g == null) + { + g = _hitTestedGlyph; + } + + Behavior behavior = GetAppropriateBehavior(g); + if (behavior == null) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tNo behavior, returning"); + return; + } + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tForwarding to behavior"); + behavior.OnDragEnter(g, e); + + if (g != null && g is ControlBodyGlyph && e.Effect == DragDropEffects.None) + { + _dragEnterReplies[g] = this; // dummy value, we just need to set something. + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tCalled DragEnter on this glyph. Caching"); + } + } + + private void OnDragLeave(Glyph g, EventArgs e) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "BS::DragLeave"); + // This is normally cleared on OnMouseUp, but we might not get an OnMouseUp to clear it. + // So let's make sure it is really cleared when we start the drag. + _dragEnterReplies.Clear(); + + // if the AdornerWindow receives a drag message, this fn() will be called w/o a glyph - so we'll assign the last hit tested one + if (g == null) + { + g = _hitTestedGlyph; + } + + Behavior behavior = GetAppropriateBehavior(g); + if (behavior == null) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\t No behavior returning "); + return; + } + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tBehavior found calling OnDragLeave"); + behavior.OnDragLeave(g, e); + } + + private bool OnMouseDoubleClick(MouseButtons button, Point mouseLoc) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return false; + } + return behavior.OnMouseDoubleClick(_hitTestedGlyph, button, mouseLoc); + } + + private bool OnMouseDown(MouseButtons button, Point mouseLoc) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return false; + } + return behavior.OnMouseDown(_hitTestedGlyph, button, mouseLoc); + } + + private bool OnMouseEnter(Glyph g) + { + Behavior behavior = GetAppropriateBehavior(g); + if (behavior == null) + { + return false; + } + return behavior.OnMouseEnter(g); + } + + private bool OnMouseHover(Point mouseLoc) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return false; + } + return behavior.OnMouseHover(_hitTestedGlyph, mouseLoc); + } + + private bool OnMouseLeave(Glyph g) + { + //stop tracking mouse events for MouseHover + UnHookMouseEvent(); + + Behavior behavior = GetAppropriateBehavior(g); + if (behavior == null) + { + return false; + } + return behavior.OnMouseLeave(g); + } + + private bool OnMouseMove(MouseButtons button, Point mouseLoc) + { + //hook mouse events (if we haven't already) for MouseHover + HookMouseEvent(); + + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return false; + } + return behavior.OnMouseMove(_hitTestedGlyph, button, mouseLoc); + } + + private bool OnMouseUp(MouseButtons button) + { + _dragEnterReplies.Clear(); + _validDragArgs = null; + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return false; + } + return behavior.OnMouseUp(_hitTestedGlyph, button); + } + private void HookMouseEvent() + { + if (!_trackingMouseEvent) + { + _trackingMouseEvent = true; + if (_trackMouseEvent == null) + { + _trackMouseEvent = new NativeMethods.TRACKMOUSEEVENT + { + dwFlags = NativeMethods.TME_HOVER, + hwndTrack = _adornerWindow.Handle + }; + } + SafeNativeMethods.TrackMouseEvent(_trackMouseEvent); + } + } + private void UnHookMouseEvent() + { + _trackingMouseEvent = false; + } + + private void OnDragDrop(DragEventArgs e) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "BS::OnDragDrop"); + _validDragArgs = null;//be sure to null out our cached drag args + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tNo behavior. returning"); + return; + } + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tForwarding to behavior"); + behavior.OnDragDrop(_hitTestedGlyph, e); + } + + private void PropagatePaint(PaintEventArgs pe) + { + for (int i = 0; i < _adorners.Count; i++) + { + if (!_adorners[i].Enabled) + { + continue; + } + for (int j = _adorners[i].Glyphs.Count - 1; j >= 0; j--) + { + _adorners[i].Glyphs[j].Paint(pe); + } + } + } + + [SuppressMessage("Microsoft.Performance", "CA1818:DoNotConcatenateStringsInsideLoops")] + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + private void TestHook_GetRecentSnapLines(ref Message m) + { + string snapLineInfo = string.Empty; + if (_testHook_RecentSnapLines != null) + { + foreach (string line in _testHook_RecentSnapLines) + { + snapLineInfo += line + "\n"; + } + } + TestHook_SetText(ref m, snapLineInfo); + } + + [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] + private void TestHook_SetText(ref Message m, string text) + { + if (m.LParam == IntPtr.Zero) + { + m.Result = (IntPtr)((text.Length + 1) * Marshal.SystemDefaultCharSize); + return; + } + + if (unchecked((int)(long)m.WParam) < text.Length + 1) + { + m.Result = (IntPtr)(-1); + return; + } + + // Copy the name into the given IntPtr + char[] nullChar = new char[] { (char)0 }; + byte[] nullBytes; + byte[] bytes; + + if (Marshal.SystemDefaultCharSize == 1) + { + bytes = Text.Encoding.Default.GetBytes(text); + nullBytes = Text.Encoding.Default.GetBytes(nullChar); + } + else + { + bytes = Text.Encoding.Unicode.GetBytes(text); + nullBytes = Text.Encoding.Unicode.GetBytes(nullChar); + } + + Marshal.Copy(bytes, 0, m.LParam, bytes.Length); + Marshal.Copy(nullBytes, 0, unchecked((IntPtr)((long)m.LParam + (long)bytes.Length)), nullBytes.Length); + m.Result = (IntPtr)((bytes.Length + nullBytes.Length) / Marshal.SystemDefaultCharSize); + } + + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + private void TestHook_GetAllSnapLines(ref Message m) + { + string snapLineInfo = ""; + if (!(_serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host)) + { + return; + } + + foreach (Component comp in host.Container.Components) + { + if (!(comp is Control)) + { + continue; + } + + if (host.GetDesigner(comp) is ControlDesigner designer) + { + foreach (SnapLine line in designer.SnapLines) + { + snapLineInfo += line.ToString() + "\tAssociated Control = " + designer.Control.Name + ":::"; + } + } + } + TestHook_SetText(ref m, snapLineInfo); + } + private void OnDragOver(DragEventArgs e) + { + // cache off our validDragArgs so we can re-fabricate enter/leave drag events + _validDragArgs = e; + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "BS::DragOver"); + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tNo behavior, exiting with DragDropEffects.None"); + e.Effect = DragDropEffects.None; + return; + } + if (_hitTestedGlyph == null || + (_hitTestedGlyph != null && !_dragEnterReplies.ContainsKey(_hitTestedGlyph))) + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tFound glyph, forwarding to behavior"); + behavior.OnDragOver(_hitTestedGlyph, e); + } + else + { + Debug.WriteLineIf(s_dragDropSwitch.TraceVerbose, "\tFell through"); + e.Effect = DragDropEffects.None; + } + } + + private void OnGiveFeedback(GiveFeedbackEventArgs e) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return; + } + behavior.OnGiveFeedback(_hitTestedGlyph, e); + } + + private void OnQueryContinueDrag(QueryContinueDragEventArgs e) + { + Behavior behavior = GetAppropriateBehavior(_hitTestedGlyph); + if (behavior == null) + { + return; + } + behavior.OnQueryContinueDrag(_hitTestedGlyph, e); + } + + private void OnSystemSettingChanged(object sender, EventArgs e) + { + SyncSelection(); + DesignerUtils.SyncBrushes(); + } + + private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + SyncSelection(); + DesignerUtils.SyncBrushes(); } } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionBehavior.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionBehavior.cs new file mode 100644 index 00000000000..3fcbffcc742 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionBehavior.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// This is the Behavior that represents DesignerActions for a particular control. + /// The DesignerActionBehavior is responsible for responding to the MouseDown message and either 1) selecting the control and changing the DesignerActionGlyph's image or 2) building up a chrome menu and requesting it to be shown. + /// Also, this Behavior acts as a proxy between "clicked" context menu items and the actual DesignerActions that they represent. + /// + internal sealed class DesignerActionBehavior : Behavior + { + private readonly IComponent _relatedComponent; //The component we are bound to + private readonly DesignerActionUI _parentUI; //ptr to the parenting UI, used for showing menus and setting selection + private DesignerActionListCollection _actionLists; //all the shortcuts! + private readonly IServiceProvider _serviceProvider; // we need to cache the service provider here to be able to create the panel with the proper arguments + private bool _ignoreNextMouseUp = false; + + /// + /// Constructor that calls base and caches off the action lists. + /// + internal DesignerActionBehavior(IServiceProvider serviceProvider, IComponent relatedComponent, DesignerActionListCollection actionLists, DesignerActionUI parentUI) + { + _actionLists = actionLists; + _serviceProvider = serviceProvider; + _relatedComponent = relatedComponent; + _parentUI = parentUI; + } + + /// + /// Returns the collection of DesignerActionLists this Behavior is managing. + /// These will be dynamically updated (some can be removed, new ones can be added, etc...). + /// + internal DesignerActionListCollection ActionLists + { + get => _actionLists; + set => _actionLists = value; + } + + /// + /// Returns the parenting UI (a DesignerActionUI) + /// + internal DesignerActionUI ParentUI + { + get => _parentUI; + } + + /// + /// Returns the Component that this glyph is attached to. + /// + internal IComponent RelatedComponent + { + get => _relatedComponent; + } + + /// + /// Hides the designer action panel UI. + /// + internal void HideUI() + { + ParentUI.HideDesignerActionPanel(); + } + + + internal DesignerActionPanel CreateDesignerActionPanel(IComponent relatedComponent) + { + // BUILD AND SHOW THE CHROME UI + DesignerActionListCollection lists = new DesignerActionListCollection(); + lists.AddRange(ActionLists); + + DesignerActionPanel dap = new DesignerActionPanel(_serviceProvider); + dap.UpdateTasks(lists, new DesignerActionListCollection(), string.Format(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name), null); + return dap; + } + + /// + /// Shows the designer action panel UI associated with this glyph. + /// + internal void ShowUI(Glyph g) + { + + if (!(g is DesignerActionGlyph glyph)) + { + Debug.Fail("Why are we trying to 'showui' on a glyph that's not a DesignerActionGlyph?"); + return; + } + + DesignerActionPanel dap = CreateDesignerActionPanel(RelatedComponent); + ParentUI.ShowDesignerActionPanel(RelatedComponent, dap, glyph); + } + + internal bool IgnoreNextMouseUp + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + set + { + _ignoreNextMouseUp = value; + } + } + + public override bool OnMouseDoubleClick(Glyph g, MouseButtons button, Point mouseLoc) + { + _ignoreNextMouseUp = true; + return true; + } + + public override bool OnMouseDown(Glyph g, MouseButtons button, Point mouseLoc) + { + return (!ParentUI.IsDesignerActionPanelVisible); + } + + + /// + /// In response to a MouseUp, we will either 1) select the Glyph and control if not selected, or 2) Build up our context menu representing our DesignerActions and show it. + /// + public override bool OnMouseUp(Glyph g, MouseButtons button) + { + if (button != MouseButtons.Left || ParentUI == null) + { + return true; + } + bool returnValue = true; + if (ParentUI.IsDesignerActionPanelVisible) + { + HideUI(); + } + else if (!_ignoreNextMouseUp) + { + if (_serviceProvider != null) + { + ISelectionService selectionService = (ISelectionService)_serviceProvider.GetService(typeof(ISelectionService)); + if (selectionService != null) + { + if (selectionService.PrimarySelection != RelatedComponent) + { + List componentList = new List + { + RelatedComponent + }; + selectionService.SetSelectedComponents(componentList, SelectionTypes.Primary); + } + } + } + ShowUI(g); + } + else + { + returnValue = false; + } + _ignoreNextMouseUp = false; + return returnValue; + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionGlyph.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionGlyph.cs new file mode 100644 index 00000000000..0955fae153d --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionGlyph.cs @@ -0,0 +1,263 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// This Glyph represents the UI appended to a control when DesignerActions are available. Each image that represents these states are demand created. + /// This is done because it is entirely possible that a DesignerActionGlyph will only ever be in one of these states during its lifetime... kind of sad really. + /// + internal sealed class DesignerActionGlyph : Glyph + { + + internal const int CONTROLOVERLAP_X = 5; //number of pixels the anchor should be offset to the left of the control's upper-right + internal const int CONTROLOVERLAP_Y = 2; //number of pixels the anchor overlaps the control in the y-direction + + private Rectangle bounds; //the bounds of our glyph + private Adorner adorner; //A ptr back to our adorner - so when we decide to change state, we can invalidate + private bool mouseOver; //on mouse over, we shade our image differently, this is used to track that state + private Rectangle alternativeBounds = Rectangle.Empty; //if !empty, this represents the bounds of the tray control this gyph is related to + private Control alternativeParent; //if this is valid - then the glyph will invalidate itself here instead of on the adorner + private bool insidePaint; + + private DockStyle dockStyle; + private Bitmap glyphImageClosed; + private Bitmap glyphImageOpened; + + /// + /// Constructor that passes empty alternative bounds and parents. + /// Typically this is done for control on the designer's surface since component tray glyphs will have these alternative values. + /// + public DesignerActionGlyph(DesignerActionBehavior behavior, Adorner adorner) : + this(behavior, adorner, Rectangle.Empty, null) + { } + public DesignerActionGlyph(DesignerActionBehavior behavior, Rectangle alternativeBounds, Control alternativeParent) : + this(behavior, null, alternativeBounds, alternativeParent) + { } + + /// + /// Constructor that sets the dropdownbox size, creates a our hottrack brush and invalidates the glyph (to configure location). + /// + private DesignerActionGlyph(DesignerActionBehavior behavior, Adorner adorner, Rectangle alternativeBounds, Control alternativeParent) : + base(behavior) + { + this.adorner = adorner; + this.alternativeBounds = alternativeBounds; + this.alternativeParent = alternativeParent; + Invalidate(); + } + + /// + /// Returns the bounds of our glyph. This is used by the related Behavior to determine where to show the contextmenu (list of actions). + /// + public override Rectangle Bounds + { + get + { + return bounds; + } + } + + public DockStyle DockEdge + { + get + { + return dockStyle; + } + set + { + if (dockStyle != value) + { + dockStyle = value; + } + } + } + + public bool IsInComponentTray + { + get + { + return (adorner == null); // adorner and alternative bounds are exclusive + } + } + + /// + /// Standard hit test logic that returns true if the point is contained within our bounds. This is also used to manage out mouse over state. + /// + public override Cursor GetHitTest(Point p) + { + if (bounds.Contains(p)) + { + MouseOver = true; + return Cursors.Default; + } + + MouseOver = false; + return null; + } + + /// + /// Returns an image representing the + /// + private Image GlyphImageClosed + { + get + { + if (glyphImageClosed == null) + { + glyphImageClosed = new Bitmap(typeof(DesignerActionGlyph), "Close_left.bmp"); + glyphImageClosed.MakeTransparent(Color.Magenta); + if (DpiHelper.IsScalingRequired) + { + DpiHelper.ScaleBitmapLogicalToDevice(ref glyphImageClosed); + } + } + + return glyphImageClosed; + } + } + + private Image GlyphImageOpened + { + get + { + if (glyphImageOpened == null) + { + glyphImageOpened = new Bitmap(typeof(DesignerActionGlyph), "Open_left.bmp"); + glyphImageOpened.MakeTransparent(Color.Magenta); + if (DpiHelper.IsScalingRequired) + { + DpiHelper.ScaleBitmapLogicalToDevice(ref glyphImageOpened); + } + } + + return glyphImageOpened; + } + } + internal void InvalidateOwnerLocation() + { + if (alternativeParent != null) + { // alternative parent and adoner are exclusive... + alternativeParent.Invalidate(bounds); + } + else + { + adorner.Invalidate(bounds); + } + } + + /// + /// Called when the state for this DesignerActionGlyph changes. Or when the related component's size or location change. Here, we re-calculate the Glyph's bounds and change our image. + /// + internal void Invalidate() + { + IComponent relatedComponent = ((DesignerActionBehavior)Behavior).RelatedComponent; + + Point topRight = Point.Empty; + + //handle the case that our comp is a control + if (relatedComponent is Control relatedControl && !(relatedComponent is ToolStripDropDown) && adorner != null) + { + topRight = adorner.BehaviorService.ControlToAdornerWindow(relatedControl); + topRight.X += relatedControl.Width; + } + //ISSUE: we can't have this special cased here - we should find a more + //generic approach to solving this problem + //special logic here for our comp being a toolstrip item + else + { + // update alternative bounds if possible... + if (alternativeParent is ComponentTray compTray) + { + ComponentTray.TrayControl trayControl = compTray.GetTrayControlFromComponent(relatedComponent); + if (trayControl != null) + { + alternativeBounds = trayControl.Bounds; + } + } + Rectangle newRect = DesignerUtils.GetBoundsForNoResizeSelectionType(alternativeBounds, SelectionBorderGlyphType.Top); + topRight.X = newRect.Right; + topRight.Y = newRect.Top; + } + + topRight.X -= (GlyphImageOpened.Width + CONTROLOVERLAP_X); + topRight.Y -= (GlyphImageOpened.Height - CONTROLOVERLAP_Y); + bounds = (new Rectangle(topRight.X, topRight.Y, GlyphImageOpened.Width, GlyphImageOpened.Height)); + + + } + + /// + /// Used to manage the mouse-pointer-is-over-glyph state. If this is true, then we will shade our BoxImage in the Paint logic. + /// + private bool MouseOver + { + get + { + return mouseOver; + } + set + { + if (mouseOver != value) + { + mouseOver = value; + + InvalidateOwnerLocation(); + } + } + } + + /// + /// Responds to a paint event. This Glyph will paint its current image and, if MouseHover is true, we'll paint over the image with the 'hoverBrush'. + /// + public override void Paint(PaintEventArgs pe) + { + Image image; + if (Behavior is DesignerActionBehavior) + { + if (insidePaint) + { + return; + } + + IComponent panelComponent = ((DesignerActionUI)((DesignerActionBehavior)Behavior).ParentUI).LastPanelComponent; + IComponent relatedComponent = ((DesignerActionBehavior)Behavior).RelatedComponent; + + if (panelComponent != null && panelComponent == relatedComponent) + { + image = GlyphImageOpened; + } + else + { + image = GlyphImageClosed; + } + try + { + insidePaint = true; + pe.Graphics.DrawImage(image, bounds.Left, bounds.Top); + if (MouseOver || (panelComponent != null && panelComponent == relatedComponent)) + { + pe.Graphics.FillRectangle(DesignerUtils.HoverBrush, Rectangle.Inflate(bounds, -1, -1)); + } + } + finally + { + insidePaint = false; + } + } + } + + /// + /// Called by the ComponentTray when a tray control changes location. + /// + internal void UpdateAlternativeBounds(Rectangle newBounds) + { + alternativeBounds = newBounds; + Invalidate(); + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionKeyboardBehavior.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionKeyboardBehavior.cs new file mode 100644 index 00000000000..21ff6c6082e --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DesignerActionKeyboardBehavior.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel.Design; +using System.Diagnostics; + +namespace System.Windows.Forms.Design.Behavior +{ + internal sealed class DesignerActionKeyboardBehavior : Behavior + { + private readonly DesignerActionPanel _panel; + private readonly IMenuCommandService _menuService; + private readonly DesignerActionUIService _daUISvc; + private static readonly Guid s_vSStandardCommandSet97 = new Guid("{5efc7975-14bc-11cf-9b2b-00aa00573819}"); + + public DesignerActionKeyboardBehavior(DesignerActionPanel panel, IServiceProvider serviceProvider, BehaviorService behaviorService) : + base(true, behaviorService) + { + _panel = panel; + if (serviceProvider != null) + { + _menuService = serviceProvider.GetService(typeof(IMenuCommandService)) as IMenuCommandService; + Debug.Assert(_menuService != null, "we should have found a menu service here..."); + _daUISvc = serviceProvider.GetService(typeof(DesignerActionUIService)) as DesignerActionUIService; + } + } + // THIS should not stay here, creation of a custom command or of the real thing should be handled in the designeractionpanel itself + public override MenuCommand FindCommand(CommandID commandId) + { + if (_panel != null && _menuService != null) + { + // if the command we're looking for is handled by the panel, just tell VS that this command is disabled. otherwise let it through as usual... + foreach (CommandID candidateCommandId in _panel.FilteredCommandIDs) + { + // VisualStudio shell implements a mutable derived class from the base CommandID. The mutable class compares overridden properties instead of the read-only backing fields when testing equality of command IDs. Thus Equals method is asymmetrical derived class's override that compares properties is the accurate one. + if (commandId.Equals(candidateCommandId)) + { + MenuCommand dummyMC = new MenuCommand(delegate { }, commandId) + { + Enabled = false + }; + return dummyMC; + } + } + // in case of a ctrl-tab we need to close the DAP + if (_daUISvc != null && commandId.Guid == DesignerActionKeyboardBehavior.s_vSStandardCommandSet97 && commandId.ID == 1124) + { + _daUISvc.HideUI(null); + } + } + return base.FindCommand(commandId); // this will route the request to the parent behavior + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DragAssistanceManager.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DragAssistanceManager.cs new file mode 100644 index 00000000000..7cc34cde7a5 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DragAssistanceManager.cs @@ -0,0 +1,1242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// The DragAssistanceManager, for lack of a better name, is responsible for integrating SnapLines into the DragBehavior. + /// At the beginning of a DragBehavior this class is instantiated and at every mouse move this class is called and given the opportunity to adjust the position of the drag. + /// The DragAssistanceManager needs to work as fast as possible - so not to interupt a drag operation. + /// Because of this, this class has many global variables that are re-used, in hopes to limit the # of allocations per mouse move / drag operation. + /// Also, for loops are used extensively (instead of foreach calls) to eliminate the creation of an enumerator. + /// + [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] + internal sealed class DragAssistanceManager + { + private readonly BehaviorService _behaviorService; + private readonly IServiceProvider _serviceProvider; + private readonly Graphics _graphics; // graphics to the adornerwindow + private readonly IntPtr _rootComponentHandle; // used for mapping window points of nested controls + private Point _dragOffset; // the offset from the new drag pos compared to the last + private Rectangle _cachedDragRect; // used to store drag rect between erasing & waiting to render + + private readonly Pen _edgePen = SystemPens.Highlight; + private readonly bool _disposeEdgePen = false; + private readonly Pen _baselinePen = new Pen(Color.Fuchsia); + + // These are global lists of all the existing vertical and hoirizontal snaplines on the designer's surface excluding the targetControl. + // All SnapLine coords in these lists have been properly adjusted for the AdornerWindow coords. + private readonly ArrayList _verticalSnapLines = new ArrayList(); + private readonly ArrayList _horizontalSnapLines = new ArrayList(); + + // These are SnapLines that represent our target control. + private readonly ArrayList _targetVerticalSnapLines = new ArrayList(); + private readonly ArrayList _targetHorizontalSnapLines = new ArrayList(); + + // This is a list of all the different type of SnapLines our target control has. When compiling our global SnapLine lists, if we see a SnapLineType that doesn't exist on our target - we can safely ignore it + private readonly ArrayList _targetSnapLineTypes = new ArrayList(); + + // These are created in our init() method (so we don't have to recreate them for every mousemove). + // These arrays represent the closest distance to any snap point on our target control. Once these are calculated + // - we can: 1) remove anything > than snapDistance and 2) determine the smallest distance overall + private int[] _verticalDistances; + private int[] _horizontalDistances; + + // These are cleared and populated on every mouse move. These lists contain all the new vertical and horizontal lines we need to draw. + // At the end of each mouse move - these lines are stored off in the vertLines and horzLines arrays. + //This way we can keep track of old snap lines and can avoid erasing and redrawing the same line. HA. + private readonly ArrayList _tempVertLines = new ArrayList(); + private readonly ArrayList _tempHorzLines = new ArrayList(); + private Line[] _vertLines = new Line[0]; + private Line[] _horzLines = new Line[0]; + + // When we draw snap lines - we only draw lines from the targetControl to the control we're snapping to. To do this, we'll keep a hashtable... + // format: snapLineToBounds[SnapLine]=ControlBounds. + private readonly Hashtable _snapLineToBounds = new Hashtable(); + + // We remember the last set of (vert & horz) lines we draw so that we can push them to the beh. svc. From there, if we receive a test hook message requesting these - we got 'em + private Line[] _recentLines; + + private readonly Image _backgroundImage; // instead of calling .invalidate on the windows below us, we'll just draw over w/the background image + private const int SnapDistance = 8; // default snapping distance (pixels) + private int _snapPointX, _snapPointY; // defines the snap adjustment that needs to be made during the mousemove/drag operation + + private const int INVALID_VALUE = 0x1111; //used to represent 'un-set' distances + + private readonly bool _resizing; // Are we resizing? + private readonly bool _ctrlDrag; // Are we in a ctrl-drag? + + /// + /// Internal constructor called that only takes a service provider. + /// Here it is assumed that all painting will be done to the AdornerWindow and that there are no target controsl to exclude from snapping. + /// + internal DragAssistanceManager(IServiceProvider serviceProvider) : this(serviceProvider, null, null, null, false, false) + { + } + + /// + /// Internal constructor that takes the service provider and the list of dragCompoents. + /// + internal DragAssistanceManager(IServiceProvider serviceProvider, ArrayList dragComponents) : this(serviceProvider, null, dragComponents, null, false, false) + { + } + + /// + /// Internal constructor that takes the service provider, the list of dragCompoents, and a boolean indicating that we are resizing. + /// + internal DragAssistanceManager(IServiceProvider serviceProvider, ArrayList dragComponents, bool resizing) : this(serviceProvider, null, dragComponents, null, resizing, false) + { + } + + /// + /// Internal constructor called by DragBehavior. + /// + internal DragAssistanceManager(IServiceProvider serviceProvider, Graphics graphics, ArrayList dragComponents, Image backgroundImage, bool ctrlDrag) : this(serviceProvider, graphics, dragComponents, backgroundImage, false, ctrlDrag) + { + } + + /// + /// Internal constructor called by DragBehavior. + /// + [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")] + internal DragAssistanceManager(IServiceProvider serviceProvider, Graphics graphics, ArrayList dragComponents, Image backgroundImage, bool resizing, bool ctrlDrag) + { + _serviceProvider = serviceProvider; + _behaviorService = serviceProvider.GetService(typeof(BehaviorService)) as BehaviorService; + if (!(serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host) || _behaviorService == null) + { + Debug.Fail("Cannot get DesignerHost or BehaviorService"); + return; + } + + if (graphics == null) + { + _graphics = _behaviorService.AdornerWindowGraphics; + } + else + { + _graphics = graphics; + } + + if (serviceProvider.GetService(typeof(IUIService)) is IUIService uiService) + { + //Can't use 'as' here since Color is a value type + if (uiService.Styles["VsColorSnaplines"] is Color) + { + _edgePen = new Pen((Color)uiService.Styles["VsColorSnaplines"]); + _disposeEdgePen = true; + } + + if (uiService.Styles["VsColorSnaplinesTextBaseline"] is Color) + { + _baselinePen.Dispose(); + _baselinePen = new Pen((Color)uiService.Styles["VsColorSnaplinesTextBaseline"]); + } + } + _backgroundImage = backgroundImage; + _rootComponentHandle = host.RootComponent is Control ? ((Control)host.RootComponent).Handle : IntPtr.Zero; + _resizing = resizing; + _ctrlDrag = ctrlDrag; + Initialize(dragComponents, host); + } + + /// + /// Adjusts then adds each snap line the designer has to offer to either our global horizontal and vertical lists or our target lists. + /// Note that we also keep track of our target snapline types - 'cause we can safely ignore all other types. If valid target is false- then we don't yet know what we're snapping against - so we'll exclude the check below to skip unwanted snap line types. + /// + private void AddSnapLines(ControlDesigner controlDesigner, ArrayList horizontalList, ArrayList verticalList, bool isTarget, bool validTarget) + { + IList snapLines = controlDesigner.SnapLines; + //Used for padding snaplines + Rectangle controlRect = controlDesigner.Control.ClientRectangle; + //Used for all others + Rectangle controlBounds = controlDesigner.Control.Bounds; + // Now map the location + controlBounds.Location = controlRect.Location = _behaviorService.ControlToAdornerWindow(controlDesigner.Control); + // Remember the offset -- we need those later + int xOffset = controlBounds.Left; + int yOffset = controlBounds.Top; + + // THIS IS ONLY NEEDED FOR PADDING SNAPLINES + // We need to adjust the bounds to the client area. This is so that we don't include borders + titlebar in the snaplines. + // In order to add padding, we need to get the offset from the usable client area of our control and the actual origin of our control. In other words: how big is the non-client area here? + // Ex: we want to add padding on a form to the insides of the borders and below the titlebar. + Point offset = controlDesigner.GetOffsetToClientArea(); + controlRect.X += offset.X;//offset for non-client area + controlRect.Y += offset.Y;//offset for non-client area + + // Adjust each snapline to local coords and add it to our global list + foreach (SnapLine snapLine in snapLines) + { + if (isTarget) + { + // we will remove padding snaplines from targets - it doesn't make sense to snap to the target's padding lines + if (snapLine.Filter != null && snapLine.Filter.StartsWith(SnapLine.Padding)) + { + continue; + } + + if (validTarget && !_targetSnapLineTypes.Contains(snapLine.SnapLineType)) + { + _targetSnapLineTypes.Add(snapLine.SnapLineType); + } + } + else + { + if (validTarget && !_targetSnapLineTypes.Contains(snapLine.SnapLineType)) + { + continue; + } + // store off the bounds in our hashtable, so if we draw snaplines we know the length of the line we need to remember different bounds based on what type of snapline this is. + if ((snapLine.Filter != null) && snapLine.Filter.StartsWith(SnapLine.Padding)) + { + _snapLineToBounds.Add(snapLine, controlRect); + } + else + { + _snapLineToBounds.Add(snapLine, controlBounds); + } + } + + if (snapLine.IsHorizontal) + { + snapLine.AdjustOffset(yOffset); + horizontalList.Add(snapLine); + } + else + { + snapLine.AdjustOffset(xOffset); + verticalList.Add(snapLine); + } + } + } + + /// + /// Build up a distance array of all same-type-alignment pts to the closest point on our targetControl. Also, keep track of the smallest distance overall. + /// + private int BuildDistanceArray(ArrayList snapLines, ArrayList targetSnapLines, int[] distances, Rectangle dragBounds) + { + int smallestDistance = INVALID_VALUE; + int highestPriority = 0; + + for (int i = 0; i < snapLines.Count; i++) + { + SnapLine snapLine = (SnapLine)snapLines[i]; + if (IsMarginOrPaddingSnapLine(snapLine)) + { + // validate margin and padding snaplines (to make sure it intersects with the dragbounds) if not, skip this guy + if (!ValidateMarginOrPaddingLine(snapLine, dragBounds)) + { + distances[i] = INVALID_VALUE; + continue; + } + } + + int smallestDelta = INVALID_VALUE; + for (int j = 0; j < targetSnapLines.Count; j++) + { + SnapLine targetSnapLine = (SnapLine)targetSnapLines[j]; + + if (SnapLine.ShouldSnap(snapLine, targetSnapLine)) + { + int delta = targetSnapLine.Offset - snapLine.Offset; + if (Math.Abs(delta) < Math.Abs(smallestDelta)) + { + smallestDelta = delta; + } + } + } + + distances[i] = smallestDelta; + int pri = (int)((SnapLine)snapLines[i]).Priority; + + // save off this delta for the overall smallest delta! + // Need to check the priority here as well if the distance is the same. + // E.g. smallestDistance so far is 1, for a Low snapline. + // We now find another distance of -1, for a Medium snapline. + // The old check if (Math.Abs(smallestDelta) < Math.Abs(smallestDistance)) would not set smallestDistance to -1, since the ABSOLUTE values are the same. Since the return value is used to phycially move the control, we would move the control in the direction of the Low snapline, but draw the Medium snapline in the opposite direction. + if ((Math.Abs(smallestDelta) < Math.Abs(smallestDistance)) || + ((Math.Abs(smallestDelta) == Math.Abs(smallestDistance)) && (pri > highestPriority))) + { + smallestDistance = smallestDelta; + if (pri != (int)SnapLinePriority.Always) + { + highestPriority = pri; + } + } + } + return smallestDistance; + } + + /// + /// Here, we erase all of our old horizontal and vertical snaplines UNLESS they are also contained in our tempHorzLines or tempVertLines arrays - if they are - then erasing them would be redundant (since we know we want to draw them on this mousemove) + /// + private Line[] EraseOldSnapLines(Line[] lines, ArrayList tempLines) + { + if (lines != null) + { + for (int i = 0; i < lines.Length; i++) + { + Line line = lines[i]; + bool foundMatch = false; + Rectangle invalidRect; + if (tempLines != null) + { + for (int j = 0; j < tempLines.Count; j++) + { + if (line.LineType != ((Line)tempLines[j]).LineType) + { + // If the lines are not the same type, then we should forcefully try to remove it. Say you have a Panel with a Button in it. By default Panel.Padding = 0, and Button.Margin = 3. As you move the button to the left, you will first get the combined LEFT margin+padding snap line. If you keep moving the button, you will now snap to the Left edge, and you will get the Blue snapline. You now move the button back to the right, and you will immediately snap to the LEFT Padding snapline. + // But what's gonna happen. Both the old (Left) snapline, and the LEFT Padding snapline (remember these are the panels) have the same coordinates, since Panel.Padding is 0. Thus Line.GetDiffs will return a non-null diffs. BUT e.g the first line will result in an invalidRect of (x1,y1,0,0), this we end up invalidating only a small portion of the existing Blue (left) Snapline. That's actually not okay since VERTICAL (e.g. LEFT) padding snaplines actually end up getting drawn HORIZONTALLY - thus we didn't really invalidate correctly. + continue; + } + + Line[] diffs = Line.GetDiffs(line, (Line)tempLines[j]); + if (diffs != null) + { + for (int k = 0; k < diffs.Length; k++) + { + invalidRect = new Rectangle(diffs[k].x1, diffs[k].y1, diffs[k].x2 - diffs[k].x1, diffs[k].y2 - diffs[k].y1); + + invalidRect.Inflate(1, 1); + if (_backgroundImage != null) + { + _graphics.DrawImage(_backgroundImage, invalidRect, invalidRect, GraphicsUnit.Pixel); + } + else + { + _behaviorService.Invalidate(invalidRect); + } + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) + { + invalidRect = new Rectangle(line.x1, line.y1, line.x2 - line.x1, line.y2 - line.y1); + invalidRect.Inflate(1, 1); + if (_backgroundImage != null) + { + _graphics.DrawImage(_backgroundImage, invalidRect, invalidRect, GraphicsUnit.Pixel); + } + else + { + _behaviorService.Invalidate(invalidRect); + } + } + } + } + + if (tempLines != null) + { + // Now, store off all the new lines (from the temp structures), so next time around (next mousemove message) we know which lines to erase and which ones to keep + lines = new Line[tempLines.Count]; + tempLines.CopyTo(lines); + } + else + { + lines = new Line[0]; + } + return lines; + + } + + internal void EraseSnapLines() + { + EraseOldSnapLines(_vertLines, null); + EraseOldSnapLines(_horzLines, null); + } + + /// + /// This internal method returns a snap line[] representing the last SnapLines that were rendered before this algorithm was stopped (usually by an OnMouseUp). + /// This is used for storing additional toolbox drag/drop info and testing hooks. + /// + internal Line[] GetRecentLines() + { + if (_recentLines != null) + { + return _recentLines; + } + return new Line[0]; + } + + private void IdentifyAndStoreValidLines(ArrayList snapLines, int[] distances, Rectangle dragBounds, int smallestDistance) + { + int highestPriority = 1; + + //identify top pri + for (int i = 0; i < distances.Length; i++) + { + if (distances[i] == smallestDistance) + { + int pri = (int)((SnapLine)snapLines[i]).Priority; + if ((pri > highestPriority) && (pri != (int)SnapLinePriority.Always)) + {// Always is a special category + highestPriority = pri; + } + } + } + + // store all snapLines equal to the smallest distance (of the highest priority) + for (int i = 0; i < distances.Length; i++) + { + if ((distances[i] == smallestDistance) && + (((int)((SnapLine)snapLines[i]).Priority == highestPriority) || + ((int)((SnapLine)snapLines[i]).Priority == (int)SnapLinePriority.Always))) + { // always render SnapLines with Priority.Always which has the same distance. + StoreSnapLine((SnapLine)snapLines[i], dragBounds); + } + } + } + + // Returns true of this child component (off the root control) should add its snaplines to the collection + private bool AddChildCompSnaplines(IComponent comp, ArrayList dragComponents, Rectangle clipBounds, Control targetControl) + { + if (!(comp is Control control) || // has to be a control to get snaplines + (dragComponents != null && dragComponents.Contains(comp) && !_ctrlDrag) || // cannot be something that we are dragging, unless we are in a ctrlDrag + IsChildOfParent(control, targetControl) ||// cannot be a child of the control we will drag + !clipBounds.IntersectsWith(control.Bounds) || // has to be partially visible on the rootcomp's surface + control.Parent == null || // control must have a parent. + !control.Visible) + { // control itself has to be visible -- we do mean visible, not ShadowedVisible + return false; + } + + Control c = control; + if (!c.Equals(targetControl)) + { + if (_serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host) + { + if (host.GetDesigner(c) is ControlDesigner controlDesigner) + { + return controlDesigner.ControlSupportsSnaplines; + } + } + } + return true; + } + + // Returns true if we should add snaplines for this control + private bool AddControlSnaplinesWhenResizing(ControlDesigner designer, Control control, Control targetControl) + { + // do not add snaplines if we are resizing the control is a container control with AutoSize set to true and the control is the parent of the targetControl + if (_resizing && + (designer is ParentControlDesigner) && + (control.AutoSize == true) && + (targetControl != null) && + (targetControl.Parent != null) && + (targetControl.Parent.Equals(control))) + { + return false; + } + return true; + } + + /// + /// Initializes our class - we cache all snap lines for every control we can find. This is done for perf. reasons. + /// + private void Initialize(ArrayList dragComponents, IDesignerHost host) + { + + // our targetControl will always be the 0th component in our dragComponents array list (a.k.a. the primary selected component). + Control targetControl = null; + + if (dragComponents != null && dragComponents.Count > 0) + { + targetControl = dragComponents[0] as Control; + } + + Control rootControl = host.RootComponent as Control; + + // the clipping bounds will be used to ignore all controls that are completely outside of our rootcomponent's bounds -this way we won't end up snapping to controls that are not visible on the form's surface + Rectangle clipBounds = new Rectangle(0, 0, rootControl.ClientRectangle.Width, rootControl.ClientRectangle.Height); + clipBounds.Inflate(-1, -1); + + // determine the screen offset from our rootComponent to the AdornerWindow (since all drag notification coords will be in adorner window coords) + if (targetControl != null) + { + _dragOffset = _behaviorService.ControlToAdornerWindow(targetControl); + } + else + { + _dragOffset = _behaviorService.MapAdornerWindowPoint(rootControl.Handle, Point.Empty); + if (rootControl.Parent != null && rootControl.Parent.IsMirrored) + { + _dragOffset.Offset(-rootControl.Width, 0); + } + } + + if (targetControl != null) + { + bool disposeDesigner = false; + // get all the target snapline information we need to create one then + if (!(host.GetDesigner(targetControl) is ControlDesigner designer)) + { + designer = TypeDescriptor.CreateDesigner(targetControl, typeof(IDesigner)) as ControlDesigner; + if (designer != null) + { + // Make sure the control is not forced visible + designer.ForceVisible = false; + designer.Initialize(targetControl); + disposeDesigner = true; + } + } + + AddSnapLines(designer, _targetHorizontalSnapLines, _targetVerticalSnapLines, true, targetControl != null); + if (disposeDesigner) + { + designer.Dispose(); + } + } + + // get SnapLines for all our children (nested too) off our root control + foreach (IComponent comp in host.Container.Components) + { + if (!AddChildCompSnaplines(comp, dragComponents, clipBounds, targetControl)) + { + continue; + } + + if (host.GetDesigner(comp) is ControlDesigner designer) + { + if (AddControlSnaplinesWhenResizing(designer, comp as Control, targetControl)) + { + AddSnapLines(designer, _horizontalSnapLines, _verticalSnapLines, false, targetControl != null); + } + + // Does the designer have internal control designers for which we need to add snaplines (like SplitPanelContainer, ToolStripContainer) + int numInternalDesigners = designer.NumberOfInternalControlDesigners(); + + for (int i = 0; i < numInternalDesigners; i++) + { + ControlDesigner internalDesigner = designer.InternalControlDesigner(i); + if (internalDesigner != null && + AddChildCompSnaplines(internalDesigner.Component, dragComponents, clipBounds, targetControl) && + AddControlSnaplinesWhenResizing(internalDesigner, internalDesigner.Component as Control, targetControl)) + { + AddSnapLines(internalDesigner, _horizontalSnapLines, _verticalSnapLines, false, targetControl != null); + } + } + } + } + //Now that we know how many snaplines everyone has, we can create temp arrays now. Intentionally avoiding this on every mousemove. + _verticalDistances = new int[_verticalSnapLines.Count]; + _horizontalDistances = new int[_horizontalSnapLines.Count]; + } + + /// + /// Helper function that determines if the child control is related to the parent. + /// + private static bool IsChildOfParent(Control child, Control parent) + { + if (child == null || parent == null) + { + return false; + } + + Control currentParent = child.Parent; + while (currentParent != null) + { + if (currentParent.Equals(parent)) + { + return true; + } + currentParent = currentParent.Parent; + } + return false; + } + + + /// + /// Helper function that identifies margin or padding snaplines + /// + private static bool IsMarginOrPaddingSnapLine(SnapLine snapLine) + { + return snapLine.Filter != null && + (snapLine.Filter.StartsWith(SnapLine.Margin) || + snapLine.Filter.StartsWith(SnapLine.Padding)); + } + + /// + /// Returns the offset in which the targetControl's rect needs to be re-positioned (given the direction by 'directionOffset') in order to align with the nearest possible snapline. This is called by commandSet during keyboard movements to auto-snap the control around the designer. + /// + internal Point OffsetToNearestSnapLocation(Control targetControl, IList targetSnaplines, Point directionOffset) + { + _targetHorizontalSnapLines.Clear(); + _targetVerticalSnapLines.Clear(); + + //manually add our snaplines as targets + foreach (SnapLine snapline in targetSnaplines) + { + if (snapline.IsHorizontal) + { + _targetHorizontalSnapLines.Add(snapline); + } + else + { + _targetVerticalSnapLines.Add(snapline); + } + } + return OffsetToNearestSnapLocation(targetControl, directionOffset); + + } + + /// + /// Returns the offset in which the targetControl's rect needs to be re-positioned (given the direction by 'directionOffset') in order to align with the nearest possible snapline. This is called by commandSet during keyboard movements to auto-snap the control around the designer. + /// + internal Point OffsetToNearestSnapLocation(Control targetControl, Point directionOffset) + { + Point offset = Point.Empty; + Rectangle currentBounds = new Rectangle(_behaviorService.ControlToAdornerWindow(targetControl), targetControl.Size); + + if (directionOffset.X != 0) + { // movement somewhere in the x dir first, build up our distance array + BuildDistanceArray(_verticalSnapLines, _targetVerticalSnapLines, _verticalDistances, currentBounds); + + //now start with the smallest distance and find the first snapline we would intercept given our horizontal direction + int minRange = directionOffset.X < 0 ? 0 : currentBounds.X; + int maxRange = directionOffset.X < 0 ? currentBounds.Right : int.MaxValue; + + offset.X = FindSmallestValidDistance(_verticalSnapLines, _verticalDistances, minRange, maxRange, directionOffset.X); + + if (offset.X != 0) + { + // store off the line structs for actual rendering + IdentifyAndStoreValidLines(_verticalSnapLines, _verticalDistances, currentBounds, offset.X); + if (directionOffset.X < 0) + { + offset.X *= -1; + } + } + } + if (directionOffset.Y != 0) + { // movement somewhere in the y dir first, build up our distance array + BuildDistanceArray(_horizontalSnapLines, _targetHorizontalSnapLines, _horizontalDistances, currentBounds); + + // now start with the smallest distance and find the first snapline we would intercept given our horizontal direction + int minRange = directionOffset.Y < 0 ? 0 : currentBounds.Y; + int maxRange = directionOffset.Y < 0 ? currentBounds.Bottom : int.MaxValue; + + offset.Y = FindSmallestValidDistance(_horizontalSnapLines, _horizontalDistances, minRange, maxRange, directionOffset.Y); + + if (offset.Y != 0) + { + // store off the line structs for actual rendering + IdentifyAndStoreValidLines(_horizontalSnapLines, _horizontalDistances, currentBounds, offset.Y); + if (directionOffset.Y < 0) + { + offset.Y *= -1; + } + } + } + + if (!offset.IsEmpty) + { + // setup the cached info for drawing + _cachedDragRect = currentBounds; + _cachedDragRect.Offset(offset.X, offset.Y); + if (offset.X != 0) + { + _vertLines = new Line[_tempVertLines.Count]; + _tempVertLines.CopyTo(_vertLines); + } + if (offset.Y != 0) + { + _horzLines = new Line[_tempHorzLines.Count]; + _tempHorzLines.CopyTo(_horzLines); + } + } + return offset; + } + + private static int FindSmallestValidDistance(ArrayList snapLines, int[] distances, int min, int max, int direction) + { + // loop while we still have valid distance to check and try to find the smallest valid distance + while (true) + { + // get the next smallest snapline index + int snapLineIndex = SmallestDistanceIndex(distances, direction, out int distanceValue); + + if (snapLineIndex == INVALID_VALUE) + { + break; + } + + if (IsWithinValidRange(((SnapLine)snapLines[snapLineIndex]).Offset, min, max)) + { + // found it - make sure we restore the original value for rendering the snap line in the future + distances[snapLineIndex] = distanceValue; + return distanceValue; + } + } + return 0; + } + + private static bool IsWithinValidRange(int offset, int min, int max) + { + return offset > min && offset < max; + } + + private static int SmallestDistanceIndex(int[] distances, int direction, out int distanceValue) + { + distanceValue = INVALID_VALUE; + int smallestIndex = INVALID_VALUE; + + // check for valid array + if (distances.Length == 0) + { + return smallestIndex; + } + + // find the next smallest + for (int i = 0; i < distances.Length; i++) + { + // If a distance is 0 or if it is to our left and we're heading right or if it is to our right and we're heading left then we can null this value out + if (distances[i] == 0 || + distances[i] > 0 && direction > 0 || + distances[i] < 0 && direction < 0) + { + distances[i] = INVALID_VALUE; + } + + if (Math.Abs(distances[i]) < distanceValue) + { + distanceValue = Math.Abs(distances[i]); + smallestIndex = i; + } + } + + if (smallestIndex < distances.Length) + { + // return and clear the smallest one we found + distances[smallestIndex] = INVALID_VALUE; + } + + return smallestIndex; + } + + /// + /// Actually draws the snaplines based on type, location, and specified pen + /// + private void RenderSnapLines(Line[] lines, Rectangle dragRect) + { + for (int i = 0; i < lines.Length; i++) + { + Pen currentPen; + if (lines[i].LineType == LineType.Margin || lines[i].LineType == LineType.Padding) + { + currentPen = _edgePen; + + if (lines[i].x1 == lines[i].x2) + { //vertical margin + int coord = Math.Max(dragRect.Top, lines[i].OriginalBounds.Top); + coord += (Math.Min(dragRect.Bottom, lines[i].OriginalBounds.Bottom) - coord) / 2; + lines[i].y1 = lines[i].y2 = coord; + if (lines[i].LineType == LineType.Margin) + { + lines[i].x1 = Math.Min(dragRect.Right, lines[i].OriginalBounds.Right); + lines[i].x2 = Math.Max(dragRect.Left, lines[i].OriginalBounds.Left); + } + else if (lines[i].PaddingLineType == PaddingLineType.PaddingLeft) + { + lines[i].x1 = lines[i].OriginalBounds.Left; + lines[i].x2 = dragRect.Left; + } + else + { + Debug.Assert(lines[i].PaddingLineType == PaddingLineType.PaddingRight); + lines[i].x1 = dragRect.Right; + lines[i].x2 = lines[i].OriginalBounds.Right; + } + lines[i].x2--; // off by 1 adjust + } + else + { // horizontal margin + int coord = Math.Max(dragRect.Left, lines[i].OriginalBounds.Left); + coord += (Math.Min(dragRect.Right, lines[i].OriginalBounds.Right) - coord) / 2; + lines[i].x1 = lines[i].x2 = coord; + if (lines[i].LineType == LineType.Margin) + { + lines[i].y1 = Math.Min(dragRect.Bottom, lines[i].OriginalBounds.Bottom); + lines[i].y2 = Math.Max(dragRect.Top, lines[i].OriginalBounds.Top); + } + else if (lines[i].PaddingLineType == PaddingLineType.PaddingTop) + { + lines[i].y1 = lines[i].OriginalBounds.Top; + lines[i].y2 = dragRect.Top; + } + else + { + Debug.Assert(lines[i].PaddingLineType == PaddingLineType.PaddingBottom); + lines[i].y1 = dragRect.Bottom; + lines[i].y2 = lines[i].OriginalBounds.Bottom; + } + lines[i].y2--; // off by 1 adjust + } + } + else if (lines[i].LineType == LineType.Baseline) + { + currentPen = _baselinePen; + lines[i].x2 -= 1; // off by 1 adjust + } + else + { + // default to edgePen + currentPen = _edgePen; + if (lines[i].x1 == lines[i].x2) + { + lines[i].y2--; // off by 1 adjustment + } + else + { + lines[i].x2--; // off by 1 adjustment + } + } + _graphics.DrawLine(currentPen, lines[i].x1, lines[i].y1, lines[i].x2, lines[i].y2); + } + } + + /// + /// Performance improvement: Given an snapline we will render, check if it overlaps with an existing snapline. If so, combine the two. + /// + private static void CombineSnaplines(Line snapLine, ArrayList currentLines) + { + bool merged = false; + for (int i = 0; i < currentLines.Count; i++) + { + Line curLine = (Line)currentLines[i]; + Line mergedLine = Line.Overlap(snapLine, curLine); + if (mergedLine != null) + { + currentLines[i] = mergedLine; + merged = true; + } + } + if (!merged) + { + currentLines.Add(snapLine); + } + } + + /// + /// Here, we store all the SnapLines we will render. This way we can erase them when they are no longer needed. + /// + [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")] + private void StoreSnapLine(SnapLine snapLine, Rectangle dragBounds) + { + Rectangle bounds = (Rectangle)_snapLineToBounds[snapLine]; + // In order for CombineSnapelines to work correctly, we have to determine the type first + LineType type = LineType.Standard; + if (IsMarginOrPaddingSnapLine(snapLine)) + { + type = snapLine.Filter.StartsWith(SnapLine.Margin) ? LineType.Margin : LineType.Padding; + } + //propagate the baseline through to the linetype + else if (snapLine.SnapLineType == SnapLineType.Baseline) + { + type = LineType.Baseline; + } + + Line line; + if (snapLine.IsVertical) + { + line = new Line(snapLine.Offset, Math.Min(dragBounds.Top + (_snapPointY != INVALID_VALUE ? _snapPointY : 0), bounds.Top), + snapLine.Offset, Math.Max(dragBounds.Bottom + (_snapPointY != INVALID_VALUE ? _snapPointY : 0), bounds.Bottom)) + { + LineType = type + }; + + // Performance improvement: Check if the newly added line overlaps existing lines and if so, combine them. + CombineSnaplines(line, _tempVertLines); + } + else + { + line = new Line(Math.Min(dragBounds.Left + (_snapPointX != INVALID_VALUE ? _snapPointX : 0), bounds.Left), snapLine.Offset, + Math.Max(dragBounds.Right + (_snapPointX != INVALID_VALUE ? _snapPointX : 0), bounds.Right), snapLine.Offset) + { + LineType = type + }; + + // Performance improvement: Check if the newly added line overlaps existing lines and if so, combine them. + CombineSnaplines(line, _tempHorzLines); + } + + if (IsMarginOrPaddingSnapLine(snapLine)) + { + line.OriginalBounds = bounds; + //need to know which padding line (left, right) we are storing. The original check in RenderSnapLines was wrong. It assume that the dragRect was completely within the OriginalBounds which is not necessarily true + if (line.LineType == LineType.Padding) + { + switch (snapLine.Filter) + { + case SnapLine.PaddingRight: + line.PaddingLineType = PaddingLineType.PaddingRight; + break; + case SnapLine.PaddingLeft: + line.PaddingLineType = PaddingLineType.PaddingLeft; + break; + case SnapLine.PaddingTop: + line.PaddingLineType = PaddingLineType.PaddingTop; + break; + case SnapLine.PaddingBottom: + line.PaddingLineType = PaddingLineType.PaddingBottom; + break; + default: + Debug.Fail("Unknown snapline filter type"); + break; + } + } + } + } + + /// + /// This function validates a Margin or Padding SnapLine. A valid Margin SnapLine is one that will be drawn only if the target control being dragged somehow intersects (vertically or horizontally) the coords of the given snapLine. + /// This is done so we don't start drawing margin lines when controls are large distances apart (too much mess); + /// + [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")] + private bool ValidateMarginOrPaddingLine(SnapLine snapLine, Rectangle dragBounds) + { + Rectangle bounds = (Rectangle)_snapLineToBounds[snapLine]; + if (snapLine.IsVertical) + { + if (bounds.Top < dragBounds.Top) + { + if (bounds.Top + bounds.Height < dragBounds.Top) + { + return false; + } + } + else if (dragBounds.Top + dragBounds.Height < bounds.Top) + { + return false; + } + } + else + { + if (bounds.Left < dragBounds.Left) + { + if (bounds.Left + bounds.Width < dragBounds.Left) + { + return false; + } + } + else if (dragBounds.Left + dragBounds.Width < bounds.Left) + { + return false; + } + } + // valid overlapping margin line + return true; + } + + internal Point OnMouseMove(Rectangle dragBounds, SnapLine[] snapLines) + { + bool didSnap = false; + return OnMouseMove(dragBounds, snapLines, ref didSnap, true); + } + + /// + /// Called by the DragBehavior on every mouse move. We first offset all of our drag-control's snap lines by the amount of the mouse move then follow our 2-pass heuristic to determine which SnapLines to render. + /// + internal Point OnMouseMove(Rectangle dragBounds, SnapLine[] snapLines, ref bool didSnap, bool shouldSnapHorizontally) + { + if (snapLines == null || snapLines.Length == 0) + { + return Point.Empty; + } + + _targetHorizontalSnapLines.Clear(); + _targetVerticalSnapLines.Clear(); + + // manually add our snaplines as targets + foreach (SnapLine snapline in snapLines) + { + if (snapline.IsHorizontal) + { + _targetHorizontalSnapLines.Add(snapline); + } + else + { + _targetVerticalSnapLines.Add(snapline); + } + } + return OnMouseMove(dragBounds, false, ref didSnap, shouldSnapHorizontally); + } + + /// + /// Called by the DragBehavior on every mouse move. We first offset all of our drag-control's snap lines by the amount of the mouse move then follow our 2-pass heuristic to determine which SnapLines to render. + /// + internal Point OnMouseMove(Rectangle dragBounds) + { + bool didSnap = false; + return OnMouseMove(dragBounds, true, ref didSnap, true); + } + + /// + /// Called by the resizebehavior. It needs to know whether we really snapped or not. + /// The snapPoint could be (0,0) even though we snapped. + /// + internal Point OnMouseMove(Control targetControl, SnapLine[] snapLines, ref bool didSnap, bool shouldSnapHorizontally) + { + Rectangle dragBounds = new Rectangle(_behaviorService.ControlToAdornerWindow(targetControl), targetControl.Size); + didSnap = false; + return OnMouseMove(dragBounds, snapLines, ref didSnap, shouldSnapHorizontally); + } + + /// + /// Called by the DragBehavior on every mouse move. We first offset all of our drag-control's snap lines by the amount of the mouse move then follow our 2-pass heuristic to determine which SnapLines to render. + /// + private Point OnMouseMove(Rectangle dragBounds, bool offsetSnapLines, ref bool didSnap, bool shouldSnapHorizontally) + { + _tempVertLines.Clear(); + _tempHorzLines.Clear(); + + _dragOffset = new Point(dragBounds.X - _dragOffset.X, dragBounds.Y - _dragOffset.Y); + + if (offsetSnapLines) + { + // offset our targetSnapLines by the amount we have dragged it + for (int i = 0; i < _targetHorizontalSnapLines.Count; i++) + { + ((SnapLine)_targetHorizontalSnapLines[i]).AdjustOffset(_dragOffset.Y); + } + for (int i = 0; i < _targetVerticalSnapLines.Count; i++) + { + ((SnapLine)_targetVerticalSnapLines[i]).AdjustOffset(_dragOffset.X); + } + } + + // First pass - build up a distance array of all same-type-alignment pts to the closest point on our targetControl. Also, keep track of the smallest distance overall + int smallestDistanceVert = BuildDistanceArray(_verticalSnapLines, _targetVerticalSnapLines, _verticalDistances, dragBounds); + int smallestDistanceHorz = INVALID_VALUE; + if (shouldSnapHorizontally) + { + smallestDistanceHorz = BuildDistanceArray(_horizontalSnapLines, _targetHorizontalSnapLines, _horizontalDistances, dragBounds); + } + // Second Pass! We only need to do a second pass if the smallest delta is <= SnapDistance. + // If this is the case - then we draw snap lines for every line equal to the smallest distance available in the distance array + _snapPointX = (Math.Abs(smallestDistanceVert) <= SnapDistance) ? -smallestDistanceVert : INVALID_VALUE; + _snapPointY = (Math.Abs(smallestDistanceHorz) <= SnapDistance) ? -smallestDistanceHorz : INVALID_VALUE; + + // certain behaviors (like resize) might want to know whether we really snapped or not. + // They can't check the returned snapPoint for (0,0) since that is a valid snapPoint. + didSnap = false; + + if (_snapPointX != INVALID_VALUE) + { + IdentifyAndStoreValidLines(_verticalSnapLines, _verticalDistances, dragBounds, smallestDistanceVert); + didSnap = true; + } + + if (_snapPointY != INVALID_VALUE) + { + IdentifyAndStoreValidLines(_horizontalSnapLines, _horizontalDistances, dragBounds, smallestDistanceHorz); + didSnap = true; + } + + Point snapPoint = new Point(_snapPointX != INVALID_VALUE ? _snapPointX : 0, _snapPointY != INVALID_VALUE ? _snapPointY : 0); + Rectangle tempDragRect = new Rectangle(dragBounds.Left + snapPoint.X, dragBounds.Top + snapPoint.Y, dragBounds.Width, dragBounds.Height); + + //out with the old... + _vertLines = EraseOldSnapLines(_vertLines, _tempVertLines); + _horzLines = EraseOldSnapLines(_horzLines, _tempHorzLines); + + // store this drag rect - we'll use it when we are (eventually) called back on to actually render our lines + // NOTE NOTE NOTE: If OnMouseMove is called during a resize operation, then cachedDragRect is not guaranteed to work. + // That is why I introduced RenderSnapLinesInternal(dragRect) + _cachedDragRect = tempDragRect; + // reset the dragoffset to this last location + _dragOffset = dragBounds.Location; + // this 'snapPoint' will be the amount we want the dragBehavior to shift the dragging control by ('cause we snapped somewhere) + return snapPoint; + } + + // NOTE NOTE NOTE: If OnMouseMove is called during a resize operation, then cachedDragRect is not guaranteed to work. + // That is why I introduced RenderSnapLinesInternal(dragRect) + /// + /// Called by the ResizeBehavior after it has finished drawing + /// + internal void RenderSnapLinesInternal(Rectangle dragRect) + { + _cachedDragRect = dragRect; + RenderSnapLinesInternal(); + } + + /// + /// Called by the DropSourceBehavior after it finished drawing its' draging images so that we can draw our lines on top of everything. + /// + internal void RenderSnapLinesInternal() + { + RenderSnapLines(_vertLines, _cachedDragRect); + RenderSnapLines(_horzLines, _cachedDragRect); + _recentLines = new Line[_vertLines.Length + _horzLines.Length]; + _vertLines.CopyTo(_recentLines, 0); + _horzLines.CopyTo(_recentLines, _vertLines.Length); + } + + /// + /// Clean up all of our references. + /// + internal void OnMouseUp() + { + // Here, we store off our recent snapline info to the behavior service - this is used for testing purposes + if (_behaviorService != null) + { + Line[] recent = GetRecentLines(); + string[] lines = new string[recent.Length]; + for (int i = 0; i < recent.Length; i++) + { + lines[i] = recent[i].ToString(); + } + _behaviorService.RecentSnapLines = lines; + } + + EraseSnapLines(); + + _graphics.Dispose(); + if (_disposeEdgePen && _edgePen != null) + { + _edgePen.Dispose(); + } + + if (_baselinePen != null) + { + _baselinePen.Dispose(); + } + + if (_backgroundImage != null) + { + _backgroundImage.Dispose(); + } + } + + /// + /// Our 'line' class - used to manage two points and calculate the difference between any two lines. + /// + internal class Line + { + public int x1, y1, x2, y2; + private LineType _lineType; + private PaddingLineType _paddingLineType; + private Rectangle _originalBounds; + + public LineType LineType + { + get => _lineType; + set => _lineType = value; + } + + + public Rectangle OriginalBounds + { + get => _originalBounds; + set=> _originalBounds = value; + } + + public PaddingLineType PaddingLineType + { + get => _paddingLineType; + set => _paddingLineType = value; + } + + + public Line(int x1, int y1, int x2, int y2) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + _lineType = LineType.Standard; + } + + private Line(int x1, int y1, int x2, int y2, LineType type) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + _lineType = type; + } + + public static Line[] GetDiffs(Line l1, Line l2) + { + //x's align + if (l1.x1 == l1.x2 && l1.x1 == l2.x1) + { + return new Line[2] {new Line(l1.x1, Math.Min(l1.y1, l2.y1), l1.x1, Math.Max(l1.y1, l2.y1)), + new Line(l1.x1, Math.Min(l1.y2, l2.y2), l1.x1, Math.Max(l1.y2, l2.y2))}; + } + + //y's align + if (l1.y1 == l1.y2 && l1.y1 == l2.y1) + { + return new Line[2] {new Line(Math.Min(l1.x1, l2.x1), l1.y1, Math.Max(l1.x1, l2.x1), l1.y1), + new Line(Math.Min(l1.x2, l2.x2), l1.y1, Math.Max(l1.x2, l2.x2), l1.y1)}; + } + + return null; + } + + public static Line Overlap(Line l1, Line l2) + { + // Need to be the same type + if (l1.LineType != l2.LineType) + { + return null; + } + + // only makes sense to do this for Standard and Baseline + if ((l1.LineType != LineType.Standard) && (l1.LineType != LineType.Baseline)) + { + return null; + } + + // 2 overlapping vertical lines + if ((l1.x1 == l1.x2) && (l2.x1 == l2.x2) && (l1.x1 == l2.x1)) + { + return new Line(l1.x1, Math.Min(l1.y1, l2.y1), l1.x2, Math.Max(l1.y2, l2.y2), l1.LineType); + } + + // 2 overlapping horizontal lines + if ((l1.y1 == l1.y2) && (l2.y1 == l2.y2) && (l1.y1 == l2.y2)) + { + return new Line(Math.Min(l1.x1, l2.x1), l1.y1, Math.Max(l1.x2, l2.x2), l1.y2, l1.LineType); + } + + return null; + } + + public override string ToString() + { + return "Line, type = " + _lineType + ", dims =(" + x1 + ", " + y1 + ")->(" + x2 + ", " + y2 + ")"; + } + } + + /// + /// Describes different types of lines (used for margins, etc..) + /// + internal enum LineType + { + Standard, Margin, Padding, Baseline + } + + /// + /// Describes what kind of padding line we have + /// + internal enum PaddingLineType + { + None, PaddingRight, PaddingLeft, PaddingTop, PaddingBottom + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DropSourceBehavior.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DropSourceBehavior.cs new file mode 100644 index 00000000000..37b3c8aa21c --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/DropSourceBehavior.cs @@ -0,0 +1,1288 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// The DropSourceBehavior is created by ControlDesigner when it detects that + /// a drag operation has started. This object is passed to the BehaviorService + /// and is used to route GiveFeedback and QueryContinueDrag drag/drop messages. + /// In response to GiveFeedback messages, this class will render the dragging + /// controls in real-time with the help of the DragAssistanceManager (Snaplines) + /// object or by simply snapping to grid dots. + /// + internal sealed class DropSourceBehavior : Behavior, IComparer + { + private struct DragComponent + { + public object dragComponent; //the dragComponent + public int zorderIndex; //the dragComponent's z-order index + public Point originalControlLocation; //the original control of the control in AdornerWindow coordinates + public Point draggedLocation; //the location of the component after each drag - in AdornerWindow coordinates + public Image dragImage; //bitblt'd image of control + public Point positionOffset; //control position offset from primary selection + }; + + private DragComponent[] dragComponents; + private ArrayList dragObjects; // used to initialize the DragAssistanceManager + private BehaviorDataObject data;//drag data that represents the controls we're dragging & the effect/action + private DragDropEffects allowedEffects;//initial allowed effects for the drag operation + private DragDropEffects lastEffect;//the last effect we saw (used for determining a valid drop) + + private bool targetAllowsSnapLines;//indicates if the drop target allows snaplines (flowpanels don't for ex) + private IComponent lastDropTarget;//indicates the drop target on the last 'give feedback' event + private Point lastSnapOffset;//the last snapoffset we used. + // These 2 could be different (e.g. if dropping between forms) + private BehaviorService behaviorServiceSource;//ptr back to the BehaviorService in the drop source + private BehaviorService behaviorServiceTarget;//ptr back to the BehaviorService in the drop target + + //this object will integrate SnapLines into the drag + private DragAssistanceManager dragAssistanceManager; + + private Graphics graphicsTarget;//graphics object of the adornerwindows (via BehaviorService) in drop target + + private IServiceProvider serviceProviderSource; + private IServiceProvider serviceProviderTarget; + + private Point initialMouseLoc;//original mouse location in screen coordinates + + private Image dragImage;//A single image of the controls we are actually dragging around + private Rectangle dragImageRect;//Rectangle of the dragImage -- in SOURCE AdornerWindow coordinates + private Rectangle clearDragImageRect; //Rectangle used to remember the last dragimage rect we cleared + private Point originalDragImageLocation; //original location of the drag image + private Region dragImageRegion; + + private Point lastFeedbackLocation; // the last position we got feedback at + private Control suspendedParent;//pointer to the parent that we suspended @ the beginning of the drag + private Size parentGridSize; //used to snap around to grid dots if layoutmode == SnapToGrid + private Point parentLocation;//location of parent on AdornerWindow - used for grid snap calculations + private bool shareParent = true;//do dragged components share the parent + private bool cleanedUpDrag = false; + private StatusCommandUI statusCommandUITarget;// UI for setting the StatusBar Information in the drop target + + private IDesignerHost srcHost; + private IDesignerHost destHost; + + private bool currentShowState = true; // Initially the controls are showing + private int primaryComponentIndex = -1; // Index of the primary component (control) in dragComponents + + /// + /// Constuctor that caches all needed vars for perf reasons. + /// + internal DropSourceBehavior(ICollection dragComponents, Control source, Point initialMouseLocation) + { + serviceProviderSource = source.Site as IServiceProvider; + if (serviceProviderSource == null) + { + Debug.Fail("DragBehavior could not be created because the source ServiceProvider was not found"); + return; + } + + behaviorServiceSource = (BehaviorService)serviceProviderSource.GetService(typeof(BehaviorService)); + if (behaviorServiceSource == null) + { + Debug.Fail("DragBehavior could not be created because the BehaviorService was not found"); + return; + } + + if (dragComponents == null || dragComponents.Count <= 0) + { + Debug.Fail("There are no component to drag!"); + return; + } + + srcHost = (IDesignerHost)serviceProviderSource.GetService(typeof(IDesignerHost)); + if (srcHost == null) + { + Debug.Fail("DragBehavior could not be created because the srcHost could not be found"); + return; + } + + this.data = new BehaviorDataObject(dragComponents, source, this); + this.allowedEffects = DragDropEffects.Copy | DragDropEffects.None | DragDropEffects.Move; + this.dragComponents = new DragComponent[dragComponents.Count]; + this.parentGridSize = Size.Empty; + + lastEffect = DragDropEffects.None; + lastFeedbackLocation = new Point(-1, -1); + lastSnapOffset = Point.Empty; + dragImageRect = Rectangle.Empty; + clearDragImageRect = Rectangle.Empty; + InitiateDrag(initialMouseLocation, dragComponents); + } + + /// + /// This is the initial allowed Effect to start the drag operation with. + /// + internal DragDropEffects AllowedEffects + { + get => allowedEffects; + } + + /// + /// This is the DataObject this DropSourceBehavior represents. + /// + internal DataObject DataObject + { + get => data; + } + + /// + /// Here, during our drag operation, we need to determine the offset from the dragging control's position 'dragLoc' and the parent's grid. We'll return an offset for the image to 'snap to'. + /// + private Point AdjustToGrid(Point dragLoc) + { + //location of the drag with respect to the parent + Point controlLocation = new Point(dragLoc.X - this.parentLocation.X, dragLoc.Y - this.parentLocation.Y); + Point offset = Point.Empty; + + //determine which way we need to snap + int xDelta = controlLocation.X % parentGridSize.Width; + int yDelta = controlLocation.Y % parentGridSize.Height; + + //if we're more than half way to the next grid - then snap that way, otherwise snap back + if (xDelta > parentGridSize.Width / 2) + { + offset.X = parentGridSize.Width - xDelta; + } + else + { + offset.X = -xDelta; + } + + if (yDelta > parentGridSize.Height / 2) + { + offset.Y = parentGridSize.Height - yDelta; + } + else + { + offset.Y = -yDelta; + } + + return offset; + } + + private Point MapPointFromSourceToTarget(Point pt) + { + if (srcHost != destHost && destHost != null) + { + pt = behaviorServiceSource.AdornerWindowPointToScreen(pt); + return behaviorServiceTarget.MapAdornerWindowPoint(IntPtr.Zero, pt); + } + else + { + return pt; + } + } + + + private Point MapPointFromTargetToSource(Point pt) + { + if (srcHost != destHost && destHost != null) + { + pt = behaviorServiceTarget.AdornerWindowPointToScreen(pt); + return behaviorServiceSource.MapAdornerWindowPoint(IntPtr.Zero, pt); + } + else + { + return pt; + } + } + + private void ClearAllDragImages() + { + if (dragImageRect != Rectangle.Empty) + { + Rectangle rect = dragImageRect; + rect.Location = MapPointFromSourceToTarget(rect.Location); + + if (graphicsTarget != null) + { + graphicsTarget.SetClip(rect); + } + + if (behaviorServiceTarget != null) + { + behaviorServiceTarget.Invalidate(rect); + } + + if (graphicsTarget != null) + { + graphicsTarget.ResetClip(); + } + } + } + + // Yeah this is recursive, but we also need to resite all the children of this control, and their children, and their children... + private void SetDesignerHost(Control c) + { + foreach (Control control in c.Controls) + { + SetDesignerHost(control); + } + + if (c.Site != null && !(c.Site is INestedSite) && destHost != null) + { + destHost.Container.Add(c); + } + } + + private void DropControl(int dragComponentIndex, Control dragTarget, Control dragSource, bool localDrag) + { + Control currentControl = dragComponents[dragComponentIndex].dragComponent as Control; + if (lastEffect == DragDropEffects.Copy || (srcHost != destHost && destHost != null)) + { + //between forms or copy + currentControl.Visible = true; + bool visibleState = true; + PropertyDescriptor propLoc = TypeDescriptor.GetProperties(currentControl)["Visible"]; + if (propLoc != null) + { + //store off the visible state. When adding the control to the new designer host, a new control designer will be created for the control. Since currentControl.Visible is currently FALSE (See InitiateDrag), the shadowed Visible property will be FALSE as well. This is not what we want. VSWHIDBEY# 311994 + visibleState = (bool)propLoc.GetValue(currentControl); + } + + // Hook the control to its new designerHost + SetDesignerHost(currentControl); + currentControl.Parent = dragTarget; + if (propLoc != null) + { + //Make sure and set the Visible property to the correct value + propLoc.SetValue(currentControl, visibleState); + } + } + else if (!localDrag && currentControl.Parent.Equals(dragSource)) + { + //between containers + dragSource.Controls.Remove(currentControl); + currentControl.Visible = true; + dragTarget.Controls.Add(currentControl); + } + } + + private void SetLocationPropertyAndChildIndex(int dragComponentIndex, Control dragTarget, Point dropPoint, int newIndex, bool allowSetChildIndexOnDrop) + { + PropertyDescriptor propLoc = TypeDescriptor.GetProperties(dragComponents[dragComponentIndex].dragComponent)["Location"]; + if ((propLoc != null) && (dragComponents[dragComponentIndex].dragComponent is Control currentControl)) + { + //ControlDesigner shadows the Location property. If the control is parented and the parent is a scrollable control, then it expects the Location to be in displayrectangle coordinates. At this point bounds are in clientrectangle coordinates, so we need to check if we need to adjust the coordinates. + Point pt = new Point(dropPoint.X, dropPoint.Y); + if (currentControl.Parent is ScrollableControl p) + { + Point ptScroll = p.AutoScrollPosition; + pt.Offset(-ptScroll.X, -ptScroll.Y); //always want to add the control below/right of the AutoScrollPosition + } + + propLoc.SetValue(currentControl, pt); + + // In some cases the target designer wants to maintain its own ZOrder, in that case we shouldn't try and set the childindex. FlowLayoutPanelDesigner is one such case. + if (allowSetChildIndexOnDrop) + { + dragTarget.Controls.SetChildIndex(currentControl, newIndex); + } + } + } + + /// + /// This is where we end the drag and commit the new control locations. To do this correctly, we loop through every control and find its propertyDescriptor for the Location. Then call SetValue(). After this we re-enable the adorners. Finally, we pop ourselves from the BehaviorStack. + /// + private void EndDragDrop(bool allowSetChildIndexOnDrop) + { + if (!(data.Target is Control dragTarget)) + { + return; // can't deal with a non-control drop target yet + } + + // If for some reason we couldn't get these guys, let's try and get them here + if (serviceProviderTarget == null) + { + Debug.Fail("EndDragDrop - how can serviceProviderTarget be null?"); + serviceProviderTarget = dragTarget.Site as IServiceProvider; + if (serviceProviderTarget == null) + { + Debug.Fail("EndDragDrop - how can serviceProviderTarget be null?"); + return; + } + } + + if (destHost == null) + { + Debug.Fail("EndDragDrop - how can destHost be null?"); + destHost = (IDesignerHost)serviceProviderTarget.GetService(typeof(IDesignerHost)); + if (destHost == null) + { + Debug.Fail("EndDragDrop - how can destHost be null?"); + return; + } + } + + if (behaviorServiceTarget == null) + { + Debug.Fail("EndDragDrop - how can behaviorServiceTarget be null?"); + behaviorServiceTarget = (BehaviorService)serviceProviderTarget.GetService(typeof(BehaviorService)); + if (behaviorServiceTarget == null) + { + Debug.Fail("EndDragDrop - how can behaviorServiceTarget be null?"); + return; + } + } + +#if PERFORM_AUTO_STUFF + bool successfulDrag = false; +#endif + // We use this list when doing a Drag-Copy, so that we can correctly restore state when we are done. See Copy code below. + ArrayList originalControls = null; + bool performCopy = (lastEffect == DragDropEffects.Copy); + Control dragSource = data.Source; + bool localDrag = dragSource.Equals(dragTarget); + + PropertyDescriptor targetProp = TypeDescriptor.GetProperties(dragTarget)["Controls"]; + PropertyDescriptor sourceProp = TypeDescriptor.GetProperties(dragSource)["Controls"]; + IComponentChangeService componentChangeSvcSource = (IComponentChangeService)serviceProviderSource.GetService(typeof(IComponentChangeService)); + IComponentChangeService componentChangeSvcTarget = (IComponentChangeService)serviceProviderTarget.GetService(typeof(IComponentChangeService)); + + if (dragAssistanceManager != null) + { + dragAssistanceManager.OnMouseUp(); + } + + // If we are dropping between hosts, we want to set the selection in the new host to be the components that we are dropping. VSWhidbey# 395676 ... or if we are copying + ISelectionService selSvc = null; + if (performCopy || (srcHost != destHost && destHost != null)) + { + selSvc = (ISelectionService)serviceProviderTarget.GetService(typeof(ISelectionService)); + } + + try + { + if (dragComponents != null && dragComponents.Length > 0) + { + DesignerTransaction transSource = null; + DesignerTransaction transTarget = null; + string transDesc; + + if (dragComponents.Length == 1) + { + string name = TypeDescriptor.GetComponentName(dragComponents[0].dragComponent); + if (name == null || name.Length == 0) + { + name = dragComponents[0].dragComponent.GetType().Name; + } + transDesc = string.Format(performCopy ? SR.BehaviorServiceCopyControl : SR.BehaviorServiceMoveControl, name); + } + else + { + transDesc = string.Format(performCopy ? SR.BehaviorServiceCopyControls : SR.BehaviorServiceMoveControls, dragComponents.Length); + } + + // We don't want to create a transaction in the source, if we are doing a cross-form copy + if (srcHost != null && !(srcHost != destHost && destHost != null && performCopy)) + { + transSource = srcHost.CreateTransaction(transDesc); + } + + if (srcHost != destHost && destHost != null) + { + transTarget = destHost.CreateTransaction(transDesc); + } + + try + { + ComponentTray tray = null; + int numberOfOriginalTrayControls = 0; + // If we are copying the controls, then, well, let's make a copy of'em... We then stuff the copy into the dragComponents array, since that keeps the rest of this code the same... No special casing needed. + if (performCopy) + { + // As part of a Ctrl-Drag, components might have been added to the component tray, make sure that their location gets updated as well (think ToolStrips). Get the current number of controls in the Component Tray in the target + tray = serviceProviderTarget.GetService(typeof(ComponentTray)) as ComponentTray; + numberOfOriginalTrayControls = tray != null ? tray.Controls.Count : 0; + + // Get the objects to copy + ArrayList temp = new ArrayList(); + for (int i = 0; i < dragComponents.Length; i++) + { + temp.Add(dragComponents[i].dragComponent); + } + + // Create a copy of them + temp = DesignerUtils.CopyDragObjects(temp, serviceProviderTarget) as ArrayList; + if (temp == null) + { + Debug.Fail("Couldn't create copies of the controls we are dragging."); + return; + } + + originalControls = new ArrayList(); + // And stick the copied controls back into the dragComponents array + for (int j = 0; j < temp.Count; j++) + { + // ... but save off the old controls first + originalControls.Add(dragComponents[j].dragComponent); + dragComponents[j].dragComponent = temp[j]; + } + } + + if ((!localDrag || performCopy) && componentChangeSvcSource != null && componentChangeSvcTarget != null) + { + componentChangeSvcTarget.OnComponentChanging(dragTarget, targetProp); + // If we are performing a copy, then the dragSource will not change + if (!performCopy) + { + componentChangeSvcSource.OnComponentChanging(dragSource, sourceProp); + } + } + + // We need to calculate initialDropPoint first to be able to calculate the new drop point for all controls. Need to drop it first to make sure that the Parent gets set correctly. + DropControl(primaryComponentIndex, dragTarget, dragSource, localDrag); + Point initialDropPoint = behaviorServiceSource.AdornerWindowPointToScreen(dragComponents[primaryComponentIndex].draggedLocation); + + // Tricky... initialDropPoint is the dropPoint in the source adornerwindow, which could be different than the target adornerwindow. But since we first convert it to screen coordinates, and then to client coordinates using the new parent, we end up dropping in the right spot. Cool, huh! + initialDropPoint = ((Control)dragComponents[primaryComponentIndex].dragComponent).Parent.PointToClient(initialDropPoint); + + // correct (only) the drop point for when Parent is mirrored, then use the offsets for the other controls, which were already corrected for mirroring in InitDrag + if (((Control)(dragComponents[primaryComponentIndex].dragComponent)).Parent.IsMirrored) + { + initialDropPoint.Offset(-((Control)(dragComponents[primaryComponentIndex].dragComponent)).Width, 0); + } + + // check permission to do that + Control primaryComponent = dragComponents[primaryComponentIndex].dragComponent as Control; + PropertyDescriptor propLoc = TypeDescriptor.GetProperties(primaryComponent)["Location"]; + if (primaryComponent != null && propLoc != null) + { + try + { + componentChangeSvcTarget.OnComponentChanging(primaryComponent, propLoc); + } + + catch (CheckoutException coEx) + { + if (coEx == CheckoutException.Canceled) + { + return; + } + throw; + } + } + // everything is fine, carry on... + SetLocationPropertyAndChildIndex(primaryComponentIndex, dragTarget, initialDropPoint, shareParent ? dragComponents[primaryComponentIndex].zorderIndex : 0, allowSetChildIndexOnDrop); + + if (selSvc != null) + { + selSvc.SetSelectedComponents(new object[] { dragComponents[primaryComponentIndex].dragComponent }, SelectionTypes.Primary | SelectionTypes.Replace); + } + + for (int i = 0; i < dragComponents.Length; i++) + { + if (i == primaryComponentIndex) + { + // did this one above + continue; + } + + DropControl(i, dragTarget, dragSource, localDrag); + Point dropPoint = new Point(initialDropPoint.X + dragComponents[i].positionOffset.X, initialDropPoint.Y + dragComponents[i].positionOffset.Y); + SetLocationPropertyAndChildIndex(i, dragTarget, dropPoint, shareParent ? dragComponents[i].zorderIndex : 0, allowSetChildIndexOnDrop); + if (selSvc != null) + { + selSvc.SetSelectedComponents(new object[] { dragComponents[i].dragComponent }, SelectionTypes.Add); + } + + } + + if ((!localDrag || performCopy) && componentChangeSvcSource != null && componentChangeSvcTarget != null) + { + componentChangeSvcTarget.OnComponentChanged(dragTarget, targetProp, dragTarget.Controls, dragTarget.Controls); + if (!performCopy) + { + componentChangeSvcSource.OnComponentChanged(dragSource, sourceProp, dragSource.Controls, dragSource.Controls); + } + } + + // If we did a Copy, then restore the old controls to make sure we set state correctly + if (originalControls != null) + { + for (int i = 0; i < originalControls.Count; i++) + { + dragComponents[i].dragComponent = originalControls[i]; + } + + originalControls = null; + } + + // Rearrange the Component Tray - if we have to + if (performCopy) + { + if (tray == null) + { + // the target did not have a tray already, so let's go get it - if there is one + tray = serviceProviderTarget.GetService(typeof(ComponentTray)) as ComponentTray; + } + + if (tray != null) + { + int numberOfTrayControlsAdded = tray.Controls.Count - numberOfOriginalTrayControls; + + if (numberOfTrayControlsAdded > 0) + { + ArrayList listOfTrayControls = new ArrayList(); + for (int i = 0; i < numberOfTrayControlsAdded; i++) + { + listOfTrayControls.Add(tray.Controls[numberOfOriginalTrayControls + i]); + } + + tray.UpdatePastePositions(listOfTrayControls); + } + } + } + + // We need to CleanupDrag BEFORE we commit the transaction. The reason is that cleaning up can potentially cause a layout, and then any changes that happen due to the layout would be in a separate UndoUnit. We want the D&D to be undoable in one step. + CleanupDrag(false); + if (transSource != null) + { + transSource.Commit(); + transSource = null; +#if PERFORM_AUTO_STUFF + successfulDrag = true; +#endif + } + if (transTarget != null) + { + transTarget.Commit(); + transTarget = null; + } + } + + finally + { + if (transSource != null) + { + transSource.Cancel(); + } + + if (transTarget != null) + { + transTarget.Cancel(); + } + } + } + } + finally + { + // If we did a Copy, then restore the old controls to make sure we set state correctly + if (originalControls != null) + { + for (int i = 0; i < originalControls.Count; i++) + { + dragComponents[i].dragComponent = originalControls[i]; + } + } + + // Even though we call CleanupDrag(false) twice (see above), this method guards against doing the wrong thing. + CleanupDrag(false); + + if (statusCommandUITarget != null) + { + // if selSvs is not null, then we either did a copy, or moved between forms, so use it to set the right info + statusCommandUITarget.SetStatusInformation(selSvc == null ? dragComponents[primaryComponentIndex].dragComponent as Component : selSvc.PrimarySelection as Component); + } + } + // clear the last feedback loc + lastFeedbackLocation = new Point(-1, -1); + } + + /// + /// Called by the BehaviorService when the GiveFeedback event is fired. Here, we attempt to render all of our dragging control snapshots. *After, of course, we let the DragAssistanceManager adjust the position due to any SnapLine activity. + /// + internal void GiveFeedback(object sender, GiveFeedbackEventArgs e) + { + //cache off this last effect so in QueryContinueDrag we can identify (if dropped) a valid drop operation + lastEffect = e.Effect; + //if our target is null, we can't drop anywhere, so don't even draw images + if (data.Target == null || e.Effect == DragDropEffects.None) + { + if (clearDragImageRect != dragImageRect) + { + //To avoid flashing, we only want to clear the drag images if the the dragimagerect is different than the last time we got here. I.e. if we keep dragging over an area where we are not allowed to drop, then we only have to clear the dragimages once. + ClearAllDragImages(); + clearDragImageRect = dragImageRect; + } + if (dragAssistanceManager != null) + { + dragAssistanceManager.EraseSnapLines(); + } + return; + } + + bool createNewDragAssistance = false; + Point mouseLoc = Control.MousePosition; + bool altKeyPressed = Control.ModifierKeys == Keys.Alt; + if (altKeyPressed && dragAssistanceManager != null) + { + //erase any snaplines (if we had any) + dragAssistanceManager.EraseSnapLines(); + } + + // I can't get rid of the ole-drag/drop default cursor that show's the cross-parent drag indication + if (data.Target.Equals(data.Source) && lastEffect != DragDropEffects.Copy) + { + e.UseDefaultCursors = false; + Cursor.Current = Cursors.Default; + } + else + { + e.UseDefaultCursors = true; + } + + // only do this drawing when the mouse pointer has actually moved so we don't continuously redraw and flicker like mad. + Control target = data.Target as Control; + if ((mouseLoc != lastFeedbackLocation) || (altKeyPressed && dragAssistanceManager != null)) + { + if (!data.Target.Equals(lastDropTarget)) + { + serviceProviderTarget = target.Site as IServiceProvider; + if (serviceProviderTarget == null) + { + return; + } + + IDesignerHost newDestHost = (IDesignerHost)serviceProviderTarget.GetService(typeof(IDesignerHost)); + if (newDestHost == null) + { + return; + } + targetAllowsSnapLines = true; + //check to see if the current designer participate with SnapLines + if (newDestHost.GetDesigner(target) is ControlDesigner designer && !designer.ParticipatesWithSnapLines) + { + targetAllowsSnapLines = false; + } + + statusCommandUITarget = new StatusCommandUI(serviceProviderTarget); + // Spin up new stuff if the host changes, or if this is the first time through (lastDropTarget will be null in this case) + if ((lastDropTarget == null) || (newDestHost != destHost)) + { + if (destHost != null && destHost != srcHost) + { + //re-enable all glyphs in the old host... need to do this before we get the new behaviorservice + behaviorServiceTarget.EnableAllAdorners(true); + } + + behaviorServiceTarget = (BehaviorService)serviceProviderTarget.GetService(typeof(BehaviorService)); + if (behaviorServiceTarget == null) + { + return; + } + + GetParentSnapInfo(target, behaviorServiceTarget); + //Disable the adorners in the new host, but only if this is not the source host, since that will already have been done + if (newDestHost != srcHost) + { + DisableAdorners(serviceProviderTarget, behaviorServiceTarget, true); + } + + // clear the old drag images in the old graphicsTarget + ClearAllDragImages(); + + // Build a new dragImageRegion -- but only if we are changing hosts + if (lastDropTarget != null) + { + for (int i = 0; i < dragObjects.Count; i++) + { + Control dragControl = (Control)dragObjects[i]; + + Rectangle controlRect = behaviorServiceSource.ControlRectInAdornerWindow(dragControl); + // Can't call MapPointFromSourceToTarget since we always want to do this + controlRect.Location = behaviorServiceSource.AdornerWindowPointToScreen(controlRect.Location); + controlRect.Location = behaviorServiceTarget.MapAdornerWindowPoint(IntPtr.Zero, controlRect.Location); + if (i == 0) + { + if (dragImageRegion != null) + { + dragImageRegion.Dispose(); + } + dragImageRegion = new Region(controlRect); + } + else + { + dragImageRegion.Union(controlRect); + } + } + } + + if (graphicsTarget != null) + { + graphicsTarget.Dispose(); + } + graphicsTarget = behaviorServiceTarget.AdornerWindowGraphics; + + // Always force the dragassistance manager to be created in this case. + createNewDragAssistance = true; + + destHost = newDestHost; + } + lastDropTarget = data.Target; + + } + + if (ShowHideDragControls(lastEffect == DragDropEffects.Copy) && !createNewDragAssistance) + { + createNewDragAssistance = true; + } + + // Create new dragassistancemanager if needed + if (createNewDragAssistance && behaviorServiceTarget.UseSnapLines) + { + if (dragAssistanceManager != null) + { + //erase any snaplines (if we had any) + dragAssistanceManager.EraseSnapLines(); + } + dragAssistanceManager = new DragAssistanceManager(serviceProviderTarget, graphicsTarget, dragObjects, null, lastEffect == DragDropEffects.Copy); + } + + //The new position of the primary control, i.e. where did we just drag it to + Point newPosition = new Point(mouseLoc.X - initialMouseLoc.X + dragComponents[primaryComponentIndex].originalControlLocation.X, + mouseLoc.Y - initialMouseLoc.Y + dragComponents[primaryComponentIndex].originalControlLocation.Y); + + // Map it to the target's adorner window so that we can snap correctly + newPosition = MapPointFromSourceToTarget(newPosition); + + //The new rectangle + Rectangle newRect = new Rectangle(newPosition.X, newPosition.Y, + dragComponents[primaryComponentIndex].dragImage.Width, + dragComponents[primaryComponentIndex].dragImage.Height); + + //if we have a valid snapline engine - ask it to offset our drag + if (dragAssistanceManager != null) + { + if (targetAllowsSnapLines && !altKeyPressed) + { + // Remembering the last snapoffset allows us to correctly erase snaplines, if the user subsequently holds down the Alt-Key. Remember that we don't physically move the mouse, we move the control (or rather the image of the control). So if we didn't remember the last snapoffset and the user then hit the Alt-Key, we would actually redraw the control at the actual mouse location, which would make the control "jump" which is not what the user would expect. Why does the control "jump"? Because when a control is snapped, we have offset the control relative to where the mouse is, but we have not update the physical mouse position. When the user hits the Alt-Key they expect the control to be where it was (whether snapped or not). + lastSnapOffset = dragAssistanceManager.OnMouseMove(newRect); + } + else + { + dragAssistanceManager.OnMouseMove(new Rectangle(-100, -100, 0, 0));/*just an invalid rect - so we won't snap*///); + } + } + //if we know our parent is forcing grid sizes + else if (!parentGridSize.IsEmpty) + { + lastSnapOffset = AdjustToGrid(newPosition); + } + + // Set the new location after the drag (only need to do this for the primary control) adjusted for a snap offset + newPosition.X += lastSnapOffset.X; + newPosition.Y += lastSnapOffset.Y; + + // draggedLocation is the coordinates in the source AdornerWindow. Need to do this since our original location is in those coordinates + dragComponents[primaryComponentIndex].draggedLocation = MapPointFromTargetToSource(newPosition); + + // Now draw the dragImage in the correct location + + // FIRST, INVALIDATE THE REGION THAT IS OUTSIDE OF THE DRAGIMAGERECT + + // First remember the old rect so that we can invalidate the right thing + Rectangle previousImageRect = dragImageRect; + + // This is in Source adorner window coordinates + newPosition = new Point(mouseLoc.X - initialMouseLoc.X + originalDragImageLocation.X, + mouseLoc.Y - initialMouseLoc.Y + originalDragImageLocation.Y); + + newPosition.X += lastSnapOffset.X; + newPosition.Y += lastSnapOffset.Y; + + // Store this off in Source adornerwindow coordinates + dragImageRect.Location = newPosition; + + previousImageRect.Location = MapPointFromSourceToTarget(previousImageRect.Location); + Rectangle newImageRect = dragImageRect; + newImageRect.Location = MapPointFromSourceToTarget(newImageRect.Location); + + Rectangle unionRectangle = Rectangle.Union(newImageRect, previousImageRect); + + Region invalidRegion = new Region(unionRectangle); + invalidRegion.Exclude(newImageRect); + + // SECOND, INVALIDATE THE TRANSPARENT REGION OF THE DRAGIMAGERECT + using (Region invalidDragRegion = dragImageRegion.Clone()) + { + invalidDragRegion.Translate(mouseLoc.X - initialMouseLoc.X + lastSnapOffset.X, mouseLoc.Y - initialMouseLoc.Y + lastSnapOffset.Y); + invalidDragRegion.Complement(newImageRect); + invalidDragRegion.Union(invalidRegion); +#if DEBUGDROPSOURCE + System.Threading.Thread.Sleep(750); + graphicsTarget.FillRegion(Brushes.Red, invalidDragRegion); + System.Threading.Thread.Sleep(750); +#endif + behaviorServiceTarget.Invalidate(invalidDragRegion); + } + + invalidRegion.Dispose(); + + if (graphicsTarget != null) + { + graphicsTarget.SetClip(newImageRect); + graphicsTarget.DrawImage(dragImage, newImageRect.X, newImageRect.Y); + graphicsTarget.ResetClip(); + } + + Control c = dragComponents[primaryComponentIndex].dragComponent as Control; + if (c != null) + { + // update drag position on the status bar + Point dropPoint = behaviorServiceSource.AdornerWindowPointToScreen(dragComponents[primaryComponentIndex].draggedLocation); + dropPoint = target.PointToClient(dropPoint); + + // must adjust offsets for the flipped X axis when our container and control are mirrored + if (target.IsMirrored && c.IsMirrored) + { + dropPoint.Offset(-c.Width, 0); + } + if (statusCommandUITarget != null) + { + statusCommandUITarget.SetStatusInformation(c as Component, dropPoint); + } + } + + //allow any snaplines to be drawn above our drag images as long as the alt key is not pressed and the mouse is over the root comp + if (dragAssistanceManager != null && !altKeyPressed && targetAllowsSnapLines) + { + dragAssistanceManager.RenderSnapLinesInternal(); + } + + // save off the current mouse position + lastFeedbackLocation = mouseLoc; + } + data.Target = null; + } + + + /// + /// We want to sort the dragComponents in descending z-order. We want to + /// make sure that we draw the control lowest in the z-order first, and drawing + /// the control at the top of the z-order last. + /// + /// Remember that z-order indices are in reverse order. I.e. the control + /// that is at the top of the z-order list has the lowest z-order index. + /// + int IComparer.Compare(Object x, Object y) + { + DragComponent dc1 = (DragComponent)x; + DragComponent dc2 = (DragComponent)y; + + if (dc1.zorderIndex > dc2.zorderIndex) + { + return -1; + } + else if (dc1.zorderIndex < dc2.zorderIndex) + { + return 1; + } + else + { + return 0; + } + } + + private void GetParentSnapInfo(Control parentControl, BehaviorService bhvSvc) + { + // Clear out whatever value we might have had stored off + parentGridSize = Size.Empty; + if (bhvSvc != null && !bhvSvc.UseSnapLines) + { + PropertyDescriptor snapProp = TypeDescriptor.GetProperties(parentControl)["SnapToGrid"]; + if (snapProp != null && (bool)snapProp.GetValue(parentControl)) + { + PropertyDescriptor gridProp = TypeDescriptor.GetProperties(parentControl)["GridSize"]; + if (gridProp != null) + { + //cache of the gridsize and the location of the parent on the adornerwindow + Control primaryControl = dragComponents[primaryComponentIndex].dragComponent as Control; + if (primaryControl != null) + { + this.parentGridSize = (Size)gridProp.GetValue(parentControl); + this.parentLocation = bhvSvc.MapAdornerWindowPoint(parentControl.Handle, Point.Empty); + if (parentControl.Parent != null && parentControl.Parent.IsMirrored) + { + this.parentLocation.Offset(-parentControl.Width, 0); + } + } + } + } + } + } + + private void DisableAdorners(IServiceProvider serviceProvider, BehaviorService behaviorService, bool hostChange) + { + //find our bodyglyph adorner offered by the behavior service we don't want to disable the transparent body glyphs + Adorner bodyGlyphAdorner = null; + SelectionManager selMgr = (SelectionManager)serviceProvider.GetService(typeof(SelectionManager)); + if (selMgr != null) + { + bodyGlyphAdorner = selMgr.BodyGlyphAdorner; + } + + //disable all adorners except for bodyglyph adorner + foreach (Adorner a in behaviorService.Adorners) + { + if (bodyGlyphAdorner != null && a.Equals(bodyGlyphAdorner)) + { + continue; + } + a.EnabledInternal = false; + } + behaviorService.Invalidate(); + + if (hostChange) + { + selMgr.OnBeginDrag(new BehaviorDragDropEventArgs(dragObjects)); + } + } + + /// + /// Called when the ContolDesigner starts a drag operation. + /// Here, all adorners are disabled, screen shots of all + /// related controls are taken, and the DragAssistanceManager + /// (for SnapLines) is created. + /// + private void InitiateDrag(Point initialMouseLocation, ICollection dragComps) + { + dragObjects = new ArrayList(dragComps); + DisableAdorners(serviceProviderSource, behaviorServiceSource, false); + Control primaryControl = dragObjects[0] as Control; + Control primaryParent = primaryControl != null ? primaryControl.Parent : null; + Color backColor = primaryParent != null ? primaryParent.BackColor : Color.Empty; + dragImageRect = Rectangle.Empty; + clearDragImageRect = Rectangle.Empty; + + initialMouseLoc = initialMouseLocation; + //loop through every control we need to drag, calculate the offsets and get a snapshot + for (int i = 0; i < dragObjects.Count; i++) + { + Control dragControl = (Control)dragObjects[i]; + dragComponents[i].dragComponent = dragObjects[i]; + dragComponents[i].positionOffset = new Point(dragControl.Location.X - primaryControl.Location.X, + dragControl.Location.Y - primaryControl.Location.Y); + Rectangle controlRect = behaviorServiceSource.ControlRectInAdornerWindow(dragControl); + if (dragImageRect.IsEmpty) + { + dragImageRect = controlRect; + dragImageRegion = new Region(controlRect); + } + else + { + dragImageRect = Rectangle.Union(dragImageRect, controlRect); + dragImageRegion.Union(controlRect); + } + + + //Initialize the dragged location to be the current position of the control + dragComponents[i].draggedLocation = controlRect.Location; + dragComponents[i].originalControlLocation = dragComponents[i].draggedLocation; + + //take snapshot of each control + DesignerUtils.GenerateSnapShot(dragControl, ref dragComponents[i].dragImage, i == 0 ? 2 : 1, 1, backColor); + + // The dragged components are not in any specific order. If they all share the same parent, we will sort them by their index in that parent's control's collection to preserve correct Z-order + if (primaryParent != null && shareParent) + { + dragComponents[i].zorderIndex = primaryParent.Controls.GetChildIndex(dragControl, false /*throwException*/); + if (dragComponents[i].zorderIndex == -1) + { + shareParent = false; + } + } + + } + + if (shareParent) + { + Array.Sort(dragComponents, this); + } + + // Now that we are sorted, set the primaryComponentIndex... + for (int i = 0; i < dragComponents.Length; i++) + { + if (primaryControl.Equals(dragComponents[i].dragComponent as Control)) + { + primaryComponentIndex = i; + break; + } + } + + Debug.Assert(primaryComponentIndex != -1, "primaryComponentIndex was not set!"); + //suspend layout of the parent + if (primaryParent != null) + { + suspendedParent = primaryParent; + suspendedParent.SuspendLayout(); + + // Get the parent's grid settings here + GetParentSnapInfo(suspendedParent, behaviorServiceSource); + } + + //If the thing that's being dragged is of 0 size, make the image a little bigger so that the user can see where they're dragging it. + int imageWidth = dragImageRect.Width; + if (imageWidth == 0) + { + imageWidth = 1; + } + + int imageHeight = dragImageRect.Height; + if (imageHeight == 0) + { + imageHeight = 1; + } + + dragImage = new Bitmap(imageWidth, imageHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + using (Graphics g = Graphics.FromImage(dragImage)) + { + g.Clear(Color.Chartreuse); + } + + ((Bitmap)dragImage).MakeTransparent(Color.Chartreuse); + // Gotta use 2 using's here... Too bad. + // Draw each control into the dragimage + using (Graphics g = Graphics.FromImage(dragImage)) + { + using (SolidBrush brush = new SolidBrush(primaryControl.BackColor)) + { + for (int i = 0; i < dragComponents.Length; i++) + { + Rectangle controlRect = new Rectangle(dragComponents[i].draggedLocation.X - dragImageRect.X, + dragComponents[i].draggedLocation.Y - dragImageRect.Y, + dragComponents[i].dragImage.Width, dragComponents[i].dragImage.Height); + // The background + g.FillRectangle(brush, controlRect); + // The foreground + g.DrawImage(dragComponents[i].dragImage, controlRect, + new Rectangle(0, 0, dragComponents[i].dragImage.Width, dragComponents[i].dragImage.Height), + GraphicsUnit.Pixel); + } + } + + } + originalDragImageLocation = new Point(dragImageRect.X, dragImageRect.Y); + //hide actual controls - this might cause a brief flicker, we are okay with that. + ShowHideDragControls(false); + + cleanedUpDrag = false; + + } + + internal ArrayList GetSortedDragControls(ref int primaryControlIndex) + { + + //create our list of controls-to-drag + ArrayList dragControls = new ArrayList(); + primaryControlIndex = -1; + + if ((dragComponents != null) && (dragComponents.Length > 0)) + { + primaryControlIndex = primaryComponentIndex; + for (int i = 0; i < dragComponents.Length; i++) + { + dragControls.Add(dragComponents[i].dragComponent); + } + } + + return dragControls; + } + + /// + /// Called by the BehaviorService in response to QueryContinueDrag notifications. + /// + internal void QueryContinueDrag(object sender, QueryContinueDragEventArgs e) + { + + //Clean up if the action was cancelled, or we had no effect when dropped. Otherwise EndDragDrop() will do this after the locations have been properly changed. + if (behaviorServiceSource != null && behaviorServiceSource.CancelDrag) + { + e.Action = DragAction.Cancel; + CleanupDrag(true); + return; + } + + if (e.Action == DragAction.Continue) + { + return; + } + + //Clean up if the action was cancelled, or we had no effect when dropped. Otherwise EndDragDrop() will do this after the locations have been properly changed. + if (e.Action == DragAction.Cancel || lastEffect == DragDropEffects.None) + { + CleanupDrag(true); + // QueryContinueDrag can be called before GiveFeedback in which case we will end up here because lastEffect == DragDropEffects.None. If we don't set e.Action, the drag will continue, and GiveFeedback will be called. But since we have cleaned up the drag, weird things happens (e.g. dragImageRegion has been disposed already, so we throw). So if we get here, let's make sure and cancel the drag. + e.Action = DragAction.Cancel; + } + + } + + /// + /// Changes the Visible state of the controls we are dragging. Returns whether we change state or not. + /// + internal bool ShowHideDragControls(bool show) + { + + if (currentShowState == show) + { + return false; + } + + currentShowState = show; + + if (dragComponents != null) + { + for (int i = 0; i < dragComponents.Length; i++) + { + Control c = dragComponents[i].dragComponent as Control; + if (c != null) + { + c.Visible = show; + } + } + } + + return true; + } + + internal void CleanupDrag() + { + CleanupDrag(true); + } + + internal void CleanupDrag(bool clearImages) + { + if (!cleanedUpDrag) + { + if (clearImages) + { + ClearAllDragImages(); + } + + ShowHideDragControls(true); + + try + { + if (suspendedParent != null) + { + suspendedParent.ResumeLayout(); + } + } + + finally + { + suspendedParent = null; + //re-enable all glyphs in all adorners + behaviorServiceSource.EnableAllAdorners(true); + + if (destHost != srcHost && destHost != null) + { + behaviorServiceTarget.EnableAllAdorners(true); + behaviorServiceTarget.SyncSelection(); + } + + // Layout may have caused controls to resize, which would mean their BodyGlyphs are wrong. We need to sync these. + if (behaviorServiceSource != null) + { + behaviorServiceSource.SyncSelection(); + } + + if (dragImageRegion != null) + { + dragImageRegion.Dispose(); + dragImageRegion = null; + } + + if (dragImage != null) + { + dragImage.Dispose(); + dragImage = null; + } + + if (dragComponents != null) + { + for (int i = 0; i < dragComponents.Length; i++) + { + if (dragComponents[i].dragImage != null) + { + dragComponents[i].dragImage.Dispose(); + dragComponents[i].dragImage = null; + } + } + } + + if (graphicsTarget != null) + { + graphicsTarget.Dispose(); + graphicsTarget = null; + } + cleanedUpDrag = true; + } + + } + } + + /// + /// This class extends from DataObject and carries additional + /// information such as: the list of Controls currently being + /// dragged and the drag 'Source'. + /// + internal class BehaviorDataObject : DataObject + { + private ICollection dragComponents; + private Control source; + private IComponent target; + private DropSourceBehavior sourceBehavior; + + public BehaviorDataObject(ICollection dragComponents, Control source, DropSourceBehavior sourceBehavior) : base() + { + this.dragComponents = dragComponents; + this.source = source; + this.sourceBehavior = sourceBehavior; + this.target = null; + } + + public Control Source + { + get => source; + } + + public ICollection DragComponents + { + get => dragComponents; + } + + public IComponent Target + { + get => target; + set => target = value; + } + + internal void EndDragDrop(bool allowSetChildIndexOnDrop) + { + sourceBehavior.EndDragDrop(allowSetChildIndexOnDrop); + } + + internal void CleanupDrag() + { + sourceBehavior.CleanupDrag(); + } + + internal ArrayList GetSortedDragControls(ref int primaryControlIndex) + { + return sourceBehavior.GetSortedDragControls(ref primaryControlIndex); + } + + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionBorderGlyphType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionBorderGlyphType.cs new file mode 100644 index 00000000000..bfb30c1be2a --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionBorderGlyphType.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// Describes the type of SelectionBorder the SelectionBorderGlyph represents. + /// + internal enum SelectionBorderGlyphType + { + Top, Bottom, Left, Right, Body + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionGlyphBase.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionGlyphBase.cs new file mode 100644 index 00000000000..fdf80bb9e46 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionGlyphBase.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// This is the base class for all the selection Glyphs: GrabHandle, Hidden, Locked, Selection, and Tray Glyphs. This class includes all like-operations for the Selection glyphs. + /// + internal abstract class SelectionGlyphBase : Glyph + { + protected Rectangle bounds; // defines the bounds of the selection glyph + protected Rectangle hitBounds; // defines the bounds used for hittest - it could be different than the bounds of the glyph itself + protected Cursor hitTestCursor; // the cursor returned if hit test is positive + protected SelectionRules rules; // the selection rules - defining how the control can change + + /// + /// Standard constructor. + /// + internal SelectionGlyphBase(Behavior behavior) : base(behavior) + { + } + + /// + /// Read-only property describing the SelecitonRules for these Glyphs. + /// + public SelectionRules SelectionRules + { + get => rules; + } + + /// + /// Simple hit test rule: if the point is contained within the bounds - then it is a positive hit test. + /// + public override Cursor GetHitTest(Point p) + { + if (hitBounds.Contains(p)) + { + return hitTestCursor; + } + return null; + } + + /// + /// Returns the HitTestCursor for this glyph. + /// + public Cursor HitTestCursor + { + get => hitTestCursor; + } + + /// + /// The Bounds of this glyph. + /// + public override Rectangle Bounds + { + get => bounds; + } + + /// + /// There's no paint logic on this base class. + /// + public override void Paint(PaintEventArgs pe) + { + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionManager.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionManager.cs new file mode 100644 index 00000000000..057be900027 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SelectionManager.cs @@ -0,0 +1,470 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace System.Windows.Forms.Design.Behavior +{ + /// + /// The SelectionBehavior is pushed onto the BehaviorStack in response to a positively hit tested SelectionGlyph. The SelectionBehavior performs two main tasks: 1) forward messages to the related ControlDesigner, and 2) calls upon the SelectionManager to push a potention DragBehavior. + /// + internal sealed class SelectionManager : IDisposable + { + private Adorner selectionAdorner;//used to provide all selection glyphs + private Adorner bodyAdorner;//used to track all body glyphs for each control + private BehaviorService behaviorService;//ptr back to our BehaviorService + private IServiceProvider serviceProvider;//standard service provider + private Hashtable componentToDesigner;//used for quick look up of designers related to comps + private Control rootComponent;//root component being designed + private ISelectionService selSvc;//we cache the selection service for perf. + private IDesignerHost designerHost;//we cache the designerhost for perf. + private bool needRefresh; // do we need to refresh? + private Rectangle[] prevSelectionBounds;//used to only repaint the changing part of the selection + private object prevPrimarySelection; //used to check if the primary selection changed + private Rectangle[] curSelectionBounds; + private int curCompIndex; + private DesignerActionUI designerActionUI = null; // the "container" for all things related to the designer action (smartags) UI + private bool selectionChanging; //we dont want the OnSelectionChanged to be recursively called. + + /// + /// Constructor. Here we query for necessary services and cache them for perf. reasons. We also hook to Component Added/Removed/Changed notifications so we can keep in sync when the designers' components change. Also, we create our custom Adorner and add it to the BehaviorService. + /// + public SelectionManager(IServiceProvider serviceProvider, BehaviorService behaviorService) + { + prevSelectionBounds = null; + prevPrimarySelection = null; + this.behaviorService = behaviorService; + this.serviceProvider = serviceProvider; + + selSvc = (ISelectionService)serviceProvider.GetService(typeof(ISelectionService)); + designerHost = (IDesignerHost)serviceProvider.GetService(typeof(IDesignerHost)); + + if (designerHost == null || selSvc == null) + { + Debug.Fail("SelectionManager - Host or SelSvc is null, can't continue"); + } + + //sync the BehaviorService's begindrag event + behaviorService.BeginDrag += new BehaviorDragDropEventHandler(OnBeginDrag); + //sync the BehaviorService's Synchronize event + behaviorService.Synchronize += new EventHandler(OnSynchronize); + selSvc.SelectionChanged += new EventHandler(OnSelectionChanged); + rootComponent = (Control)designerHost.RootComponent; + + // create and add both of our adorners, one for selection, one for bodies + selectionAdorner = new Adorner(); + bodyAdorner = new Adorner(); + behaviorService.Adorners.Add(bodyAdorner); + behaviorService.Adorners.Add(selectionAdorner); //adding this will cause the adorner to get setup with a ptr to the beh.svc. + + componentToDesigner = new Hashtable(); + designerHost.TransactionClosed += new DesignerTransactionCloseEventHandler(OnTransactionClosed); + // designeraction UI + if (designerHost.GetService(typeof(DesignerOptionService)) is DesignerOptionService options) + { + PropertyDescriptor p = options.Options.Properties["UseSmartTags"]; + if (p != null && p.PropertyType == typeof(bool) && (bool)p.GetValue(null)) + { + designerActionUI = new DesignerActionUI(serviceProvider, selectionAdorner); + behaviorService.DesignerActionUI = designerActionUI; + } + } + } + + /// + /// Returns the Adorner that contains all the BodyGlyphs for the current selection state. + /// + internal Adorner BodyGlyphAdorner + { + get => bodyAdorner; + } + + /// + /// There are certain cases like Adding Item to ToolStrips through InSitu Editor, where there is ParentTransaction that has to be cancelled depending upon the user action When this parent transaction is cancelled, there may be no reason to REFRESH the selectionManager which actually clears all the glyphs and readds them This REFRESH causes a lot of flicker and can be avoided by setting this property to false. Since this property is checked in the TransactionClosed, the SelectionManager won't REFRESH and hence just eat up the refresh thus avoiding unnecessary flicker. + /// + internal bool NeedRefresh + { + get => needRefresh; + set => needRefresh = value; + } + + /// + /// Returns the Adorner that contains all the BodyGlyphs for the current selection state. + /// + internal Adorner SelectionGlyphAdorner + { + get => selectionAdorner; + } + + /// + /// This method fist calls the recursive AddControlGlyphs() method. When finished, we add the final glyph(s) to the root comp. + /// + private void AddAllControlGlyphs(Control parent, ArrayList selComps, object primarySelection) + { + foreach (Control control in parent.Controls) + { + AddAllControlGlyphs(control, selComps, primarySelection); + } + + GlyphSelectionType selType = GlyphSelectionType.NotSelected; + if (selComps.Contains(parent)) + { + if (parent.Equals(primarySelection)) + { + selType = GlyphSelectionType.SelectedPrimary; + } + else + { + selType = GlyphSelectionType.Selected; + } + } + AddControlGlyphs(parent, selType); + } + + /// + /// Recursive method that goes through and adds all the glyphs of every child to our global Adorner. + /// + private void AddControlGlyphs(Control c, GlyphSelectionType selType) + { + + ControlDesigner cd = (ControlDesigner)componentToDesigner[c]; + if (cd != null) + { + ControlBodyGlyph bodyGlyph = cd.GetControlGlyphInternal(selType); + if (bodyGlyph != null) + { + bodyAdorner.Glyphs.Add(bodyGlyph); + if (selType == GlyphSelectionType.SelectedPrimary || + selType == GlyphSelectionType.Selected) + { + if (curSelectionBounds[curCompIndex] == Rectangle.Empty) + { + curSelectionBounds[curCompIndex] = bodyGlyph.Bounds; + } + else + { + curSelectionBounds[curCompIndex] = Rectangle.Union(curSelectionBounds[curCompIndex], bodyGlyph.Bounds); + } + } + } + GlyphCollection glyphs = cd.GetGlyphs(selType); + if (glyphs != null) + { + selectionAdorner.Glyphs.AddRange(glyphs); + if (selType == GlyphSelectionType.SelectedPrimary || + selType == GlyphSelectionType.Selected) + { + foreach (Glyph glyph in glyphs) + { + curSelectionBounds[curCompIndex] = Rectangle.Union(curSelectionBounds[curCompIndex], glyph.Bounds); + } + } + } + } + + if (selType == GlyphSelectionType.SelectedPrimary || selType == GlyphSelectionType.Selected) + { + curCompIndex++; + } + + } + + /// + /// Unhook all of our event notifications, clear our adorner and remove it from the Beh.Svc. + /// + // We don't need to Dispose rootComponent. + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")] + public void Dispose() + { + if (designerHost != null) + { + designerHost.TransactionClosed -= new DesignerTransactionCloseEventHandler(OnTransactionClosed); + designerHost = null; + } + + if (serviceProvider != null) + { + if (selSvc != null) + { + selSvc.SelectionChanged -= new EventHandler(OnSelectionChanged); + selSvc = null; + } + serviceProvider = null; + } + + if (behaviorService != null) + { + behaviorService.Adorners.Remove(bodyAdorner); + behaviorService.Adorners.Remove(selectionAdorner); + behaviorService.BeginDrag -= new BehaviorDragDropEventHandler(OnBeginDrag); + behaviorService.Synchronize -= new EventHandler(OnSynchronize); + behaviorService = null; + } + + if (selectionAdorner != null) + { + selectionAdorner.Glyphs.Clear(); + selectionAdorner = null; + } + + if (bodyAdorner != null) + { + bodyAdorner.Glyphs.Clear(); + bodyAdorner = null; + } + + if (designerActionUI != null) + { + designerActionUI.Dispose(); + designerActionUI = null; + } + } + + /// + /// Refreshes all selection Glyphs. + /// + public void Refresh() + { + NeedRefresh = false; + OnSelectionChanged(this, null); + } + + /// + /// When a component is added, we get the designer and add it to our hashtable for quick lookup. + /// + private void OnComponentAdded(object source, ComponentEventArgs ce) + { + IComponent component = ce.Component; + IDesigner designer = designerHost.GetDesigner(component); + if (designer is ControlDesigner) + { + componentToDesigner.Add(component, designer); + } + } + + /// + /// Before a drag, remove all glyphs that are involved in the drag operation and any that don't allow drops. + /// + private void OnBeginDrag(object source, BehaviorDragDropEventArgs e) + { + ArrayList dragComps = new ArrayList(e.DragComponents); + ArrayList glyphsToRemove = new ArrayList(); + foreach (ControlBodyGlyph g in bodyAdorner.Glyphs) + { + if (g.RelatedComponent is Control) + { + if (dragComps.Contains(g.RelatedComponent) || + !((Control)g.RelatedComponent).AllowDrop) + { + glyphsToRemove.Add(g); + } + } + } + foreach (Glyph g in glyphsToRemove) + { + bodyAdorner.Glyphs.Remove(g); + } + } + + // Called by the DropSourceBehavior when dragging into a new host + internal void OnBeginDrag(BehaviorDragDropEventArgs e) + { + OnBeginDrag(null, e); + } + + /// + /// When a component is changed - we need to refresh the selection. + /// + private void OnComponentChanged(object source, ComponentChangedEventArgs ce) + { + if (selSvc.GetComponentSelected(ce.Component)) + { + if (!designerHost.InTransaction) + { + Refresh(); + } + else + { + NeedRefresh = true; + } + } + } + + /// + /// When a component is removed - we remove the key & value from our hashtable. + /// + private void OnComponentRemoved(object source, ComponentEventArgs ce) + { + if (componentToDesigner.Contains(ce.Component)) + { + componentToDesigner.Remove(ce.Component); + } + //remove the associated designeractionpanel + if (designerActionUI != null) + { + designerActionUI.RemoveActionGlyph(ce.Component); + } + } + /// + /// Computes the region representing the difference between the old selection and the new selection. + /// + private Region DetermineRegionToRefresh(object primarySelection) + { + Region toRefresh = new Region(Rectangle.Empty); + Rectangle[] larger; + Rectangle[] smaller; + if (curSelectionBounds.Length >= prevSelectionBounds.Length) + { + larger = curSelectionBounds; + smaller = prevSelectionBounds; + } + else + { + larger = prevSelectionBounds; + smaller = curSelectionBounds; + } + + // we need to make sure all of the rects in the smaller array are accounted for. Any that don't intersect a rect in the larger array need to be included in the region to repaint. + bool[] intersected = new bool[smaller.Length]; + for (int i = 0; i < smaller.Length; i++) + { + intersected[i] = false; + } + + // determine which rects in the larger array need to be included in the region to invalidate by intersecting with rects in the smaller array. + for (int l = 0; l < larger.Length; l++) + { + bool largeIntersected = false; + Rectangle large = larger[l]; + for (int s = 0; s < smaller.Length; s++) + { + if (large.IntersectsWith(smaller[s])) + { + Rectangle small = smaller[s]; + largeIntersected = true; + if (large != small) + { + toRefresh.Union(large); + toRefresh.Union(small); + } + intersected[s] = true; + break; + } + } + if (!largeIntersected) + { + toRefresh.Union(large); + } + } + // now add any rects from the smaller array that weren't accounted for + for (int k = 0; k < intersected.Length; k++) + { + if (!intersected[k]) + { + toRefresh.Union(smaller[k]); + } + } + + using (Graphics g = behaviorService.AdornerWindowGraphics) + { + //If all that changed was the primary selection, then the refresh region was empty, but we do need to update the 2 controls. + if (toRefresh.IsEmpty(g) && primarySelection != null && !primarySelection.Equals(prevPrimarySelection)) + { + for (int i = 0; i < curSelectionBounds.Length; i++) + { + toRefresh.Union(curSelectionBounds[i]); + } + } + } + return toRefresh; + } + + /// + /// Event handler for the behaviorService's Synchronize event + /// + private void OnSynchronize(object sender, EventArgs e) + { + Refresh(); + } + + /// + /// On every selectionchange, we remove all glyphs, get the newly selected components, and re-add all glyphs back to the Adorner. + /// + private void OnSelectionChanged(object sender, EventArgs e) + { + // Note: selectionChanging would guard against a re-entrant code... Since we dont want to be in messed up state when adding new Glyphs. + if (!selectionChanging) + { + selectionChanging = true; + selectionAdorner.Glyphs.Clear(); + bodyAdorner.Glyphs.Clear(); + ArrayList selComps = new ArrayList(selSvc.GetSelectedComponents()); + object primarySelection = selSvc.PrimarySelection; + //add all control glyphs to all controls on rootComp + curCompIndex = 0; + curSelectionBounds = new Rectangle[selComps.Count]; + AddAllControlGlyphs(rootComponent, selComps, primarySelection); + if (prevSelectionBounds != null) + { + Region toUpdate = DetermineRegionToRefresh(primarySelection); + using (Graphics g = behaviorService.AdornerWindowGraphics) + { + if (!toUpdate.IsEmpty(g)) + { + selectionAdorner.Invalidate(toUpdate); + } + } + } + else + { + // There was no previous selection, so just invalidate the current selection + if (curSelectionBounds.Length > 0) + { + Rectangle toUpdate = curSelectionBounds[0]; + for (int i = 1; i < curSelectionBounds.Length; i++) + { + toUpdate = Rectangle.Union(toUpdate, curSelectionBounds[i]); + } + if (toUpdate != Rectangle.Empty) + { + selectionAdorner.Invalidate(toUpdate); + } + } + else + { + selectionAdorner.Invalidate(); + } + } + + prevPrimarySelection = primarySelection; + if (curSelectionBounds.Length > 0) + { + prevSelectionBounds = new Rectangle[curSelectionBounds.Length]; + Array.Copy(curSelectionBounds, prevSelectionBounds, curSelectionBounds.Length); + } + else + { + prevSelectionBounds = null; + } + selectionChanging = false; + } + } + + /// + /// When a transaction that involves one of our components closes, refresh to reflect any changes. + /// + private void OnTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) + { + if (e.LastTransaction && NeedRefresh) + { + Refresh(); + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SnapLine.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SnapLine.cs index a9f1d2e944a..e40fab32065 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SnapLine.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/Behavior/SnapLine.cs @@ -18,6 +18,20 @@ namespace System.Windows.Forms.Design.Behavior /// public sealed class SnapLine { + // These are used in the SnapLine filter to define custom margin/padding SnapLines. + // Margins will have special rules of equality, basically opposites will attract one another + // (ex: margin right == margin left) and paddings will be attracted to like-margins. + internal const string Margin = "Margin"; + internal const string MarginRight = Margin + ".Right"; + internal const string MarginLeft = Margin + ".Left"; + internal const string MarginBottom = Margin + ".Bottom"; + internal const string MarginTop = Margin + ".Top"; + internal const string Padding = "Padding"; + internal const string PaddingRight = Padding + ".Right"; + internal const string PaddingLeft = Padding + ".Left"; + internal const string PaddingBottom = Padding + ".Bottom"; + internal const string PaddingTop = Padding + ".Top"; + /// /// SnapLine constructor that takes the type and offset of SnapLine. /// diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/CommandSet.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/CommandSet.cs new file mode 100644 index 00000000000..4e1c9c70827 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/CommandSet.cs @@ -0,0 +1,3650 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.ComponentModel.Design.Serialization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Drawing.Design; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Windows.Forms.Design.Behavior; + +namespace System.Windows.Forms.Design +{ + /// + /// This class implements the standard set of menu commands for the form designer. This set of command is shared between the form designer (and other UI-based form packages), and composition designer, which doesn't manipulate controls. Therefore, this set of command should only contain commands that are common to both functions. + /// + internal class CommandSet : IDisposable + { + protected ISite site; + private readonly CommandSetItem[] _commandSet; + private IMenuCommandService _menuService; + private IEventHandlerService _eventService; + + // Selection service fields. We keep some state about the currently selected components so we can determine proper command enabling quickly. + private ISelectionService _selectionService; + protected int selCount; // the current selection count + protected IComponent primarySelection; // the primary selection, or null + private bool _selectionInherited; // the selection contains inherited components + protected bool controlsOnlySelection; // is the selection containing only controls or are there components in it? + private int _selectionVersion = 1; // the counter of selection changes. + + // Selection sort constants + private const int SORT_HORIZONTAL = 0; + private const int SORT_VERTICAL = 1; + private const int SORT_ZORDER = 2; + + private const string CF_DESIGNER = "CF_DESIGNERCOMPONENTS_V2"; + + //these are used for snapping control via keyboard movement + protected DragAssistanceManager dragManager = null; //point to the snapline engine (only valid between keydown and timer expiration) + private Timer _snapLineTimer; //used to track the time from when a snapline is rendered until it should expire + private BehaviorService _behaviorService; //demand created pointer to the behaviorservice + private StatusCommandUI _statusCommandUI; //Used to update the statusBar Information. + + /// + /// Creates a new CommandSet object. This object implements the set of commands that the UI.Win32 form designer offers. + /// + public CommandSet(ISite site) + { + this.site = site; + + _eventService = (IEventHandlerService)site.GetService(typeof(IEventHandlerService)); + Debug.Assert(_eventService != null, "Command set must have the event service. Is command set being initialized too early?"); + _eventService.EventHandlerChanged += new EventHandler(this.OnEventHandlerChanged); + IDesignerHost host = (IDesignerHost)site.GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host != null) + { + host.Activated += new EventHandler(this.UpdateClipboardItems); + } + + _statusCommandUI = new StatusCommandUI(site); + IUIService uiService = site.GetService(typeof(IUIService)) as IUIService; + + // Establish our set of commands + _commandSet = new CommandSetItem[] { + + // Editing commands + new CommandSetItem(this, new EventHandler(OnStatusDelete), new EventHandler(OnMenuDelete), MenuCommands.Delete, uiService), + new CommandSetItem( this, new EventHandler(OnStatusCopy), new EventHandler(OnMenuCopy), MenuCommands.Copy, uiService), + new CommandSetItem(this, new EventHandler(OnStatusCut), new EventHandler(OnMenuCut), MenuCommands.Cut, uiService), + new ImmediateCommandSetItem(this, new EventHandler(OnStatusPaste), new EventHandler(OnMenuPaste), MenuCommands.Paste, uiService), + + // Miscellaneous commands + new CommandSetItem(this, new EventHandler(OnStatusSelectAll), new EventHandler(OnMenuSelectAll), MenuCommands.SelectAll, true,uiService), + new CommandSetItem(this, new EventHandler(OnStatusAlways), new EventHandler(OnMenuDesignerProperties), MenuCommands.DesignerProperties, uiService), + + // Keyboard commands + new CommandSetItem(this, new EventHandler(OnStatusAlways), new EventHandler(OnKeyCancel), MenuCommands.KeyCancel, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAlways), new EventHandler(OnKeyCancel), MenuCommands.KeyReverseCancel, uiService), + new CommandSetItem(this, new EventHandler(OnStatusPrimarySelection), new EventHandler(OnKeyDefault), MenuCommands.KeyDefaultAction, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyMoveUp, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyMoveDown, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyMoveLeft, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyMoveRight, true), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyNudgeUp, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyNudgeDown, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyNudgeLeft, true, uiService), + new CommandSetItem(this, new EventHandler(OnStatusAnySelection), new EventHandler(OnKeyMove), MenuCommands.KeyNudgeRight, true, uiService), + }; + + _selectionService = (ISelectionService)site.GetService(typeof(ISelectionService)); + Debug.Assert(_selectionService != null, "CommandSet relies on the selection service, which is unavailable."); + if (_selectionService != null) + { + _selectionService.SelectionChanged += new EventHandler(this.OnSelectionChanged); + } + + _menuService = (IMenuCommandService)site.GetService(typeof(IMenuCommandService)); + if (_menuService != null) + { + for (int i = 0; i < _commandSet.Length; i++) + { + _menuService.AddCommand(_commandSet[i]); + } + } + + // Now setup the default command GUID for this designer. This GUID is also used in our toolbar definition file to identify toolbars we own. + // We store the GUID in a command ID here in the dictionary of the root component. Our host may pull this GUID out and use it. + IDictionaryService ds = site.GetService(typeof(IDictionaryService)) as IDictionaryService; + Debug.Assert(ds != null, "No dictionary service"); + if (ds != null) + { + ds.SetValue(typeof(CommandID), new CommandID(new Guid("BA09E2AF-9DF2-4068-B2F0-4C7E5CC19E2F"), 0)); + } + } + + /// + /// Demand creates a pointer to the BehaviorService + /// + protected BehaviorService BehaviorService + { + get + { + if (_behaviorService == null) + { + _behaviorService = GetService(typeof(BehaviorService)) as BehaviorService; + } + return _behaviorService; + } + } + + /// + /// Retrieves the menu command service, which the command set typically uses quite a bit. + /// + protected IMenuCommandService MenuService + { + get + { + if (_menuService == null) + { + _menuService = (IMenuCommandService)GetService(typeof(IMenuCommandService)); + } + + return _menuService; + } + } + + /// + /// Retrieves the selection service, which the command set typically uses quite a bit. + /// + protected ISelectionService SelectionService + { + get => _selectionService; + } + + protected int SelectionVersion + { + get => _selectionVersion; + } + + /// + /// This property demand creates our snaplinetimer used to track how long we'll leave snaplines on the screen before erasing them + /// + protected Timer SnapLineTimer + { + get + { + if (_snapLineTimer == null) + { + //instantiate our snapline timer + _snapLineTimer = new Timer + { + Interval = DesignerUtils.SNAPELINEDELAY + }; + _snapLineTimer.Tick += new EventHandler(this.OnSnapLineTimerExpire); + } + return _snapLineTimer; + } + } + + /// + /// Checks if an object supports ComponentEditors, and optionally launches the editor. + /// + private bool CheckComponentEditor(object obj, bool launchEditor) + { + if (obj is IComponent) + { + try + { + if (!launchEditor) + { + return true; + } + + ComponentEditor editor = (ComponentEditor)TypeDescriptor.GetEditor(obj, typeof(ComponentEditor)); + if (editor == null) + { + return false; + } + + bool success = false; + IComponentChangeService changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + + if (changeService != null) + { + try + { + changeService.OnComponentChanging(obj, null); + } + catch (CheckoutException coEx) + { + if (coEx == CheckoutException.Canceled) + { + return false; + } + throw coEx; + } + catch + { + Debug.Fail("non-CLS compliant exception"); + throw; + } + } + + WindowsFormsComponentEditor winEditor = editor as WindowsFormsComponentEditor; + if (winEditor != null) + { + IWin32Window parent = null; + //REVIEW: This smells wrong + if (obj is IWin32Window) + { +#pragma warning disable 1717 // assignment to self + parent = (IWin32Window)parent; +#pragma warning restore 1717 + } + + success = winEditor.EditComponent(obj, parent); + } + else + { + success = editor.EditComponent(obj); + } + + if (success && changeService != null) + { + // Now notify the change service that the change was successful. + changeService.OnComponentChanged(obj, null, null, null); + } + return true; + } + catch (Exception ex) + { + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + } + return false; + } + + /// + /// Disposes of this object, removing all commands from the menu service. + /// + // We don't need to Dispose snapLineTimer + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")] + public virtual void Dispose() + { + if (_menuService != null) + { + for (int i = 0; i < _commandSet.Length; i++) + { + _menuService.RemoveCommand(_commandSet[i]); + _commandSet[i].Dispose(); + } + _menuService = null; + } + + if (_selectionService != null) + { + _selectionService.SelectionChanged -= new EventHandler(this.OnSelectionChanged); + _selectionService = null; + } + + if (_eventService != null) + { + _eventService.EventHandlerChanged -= new EventHandler(this.OnEventHandlerChanged); + _eventService = null; + } + + IDesignerHost host = (IDesignerHost)site.GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host != null) + { + host.Activated -= new EventHandler(this.UpdateClipboardItems); + } + + if (_snapLineTimer != null) + { + _snapLineTimer.Stop(); + _snapLineTimer.Tick -= new EventHandler(this.OnSnapLineTimerExpire); + _snapLineTimer = null; + } + + EndDragManager(); + _statusCommandUI = null; + site = null; + } + + /// + /// Properly cleans up our drag engine. + /// + protected void EndDragManager() + { + if (dragManager != null) + { + if (_snapLineTimer != null) + { + _snapLineTimer.Stop(); + } + + dragManager.EraseSnapLines(); + dragManager.OnMouseUp(); + dragManager = null; + } + } + + /// + /// Filters the set of selected components. The selection service will retrieve all components that are currently selected. This method allows you to filter this set down to components that match your criteria. The selectionRules parameter must contain one or more flags from the SelectionRules class. These flags allow you to constrain the set of selected objects to visible, movable, sizeable or all objects. + /// + private object[] FilterSelection(object[] components, SelectionRules selectionRules) + { + object[] selection = null; + if (components == null) + return new object[0]; + + // Mask off any selection object that doesn't adhere to the given ruleset. We can ignore this if the ruleset is zero, as all components would be accepted. + if (selectionRules != SelectionRules.None) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + ArrayList list = new ArrayList(); + foreach (IComponent comp in components) + { + if (host.GetDesigner(comp) is ControlDesigner des && (des.SelectionRules & selectionRules) == selectionRules) + { + list.Add(comp); + } + } + selection = list.ToArray(); + } + } + return selection ?? (new object[0]); + } + + /// + /// Used to retrieve the selection for a copy. The default implementation retrieves the current selection. + /// + protected virtual ICollection GetCopySelection() + { + ICollection selectedComponents = SelectionService.GetSelectedComponents(); + bool sort = false; + object[] comps = new object[selectedComponents.Count]; + selectedComponents.CopyTo(comps, 0); + foreach (object comp in comps) + { + if (comp is Control) + { + sort = true; + break; + } + + } + if (sort) + { + SortSelection(comps, SORT_ZORDER); + } + selectedComponents = comps; + IDesignerHost host = (IDesignerHost)site.GetService(typeof(IDesignerHost)); + if (host != null) + { + ArrayList copySelection = new ArrayList(); + foreach (IComponent comp in selectedComponents) + { + copySelection.Add(comp); + GetAssociatedComponents(comp, host, copySelection); + } + selectedComponents = copySelection; + } + return selectedComponents; + } + + private void GetAssociatedComponents(IComponent component, IDesignerHost host, ArrayList list) + { + if (!(host.GetDesigner(component) is ComponentDesigner designer)) + { + return; + } + + foreach (IComponent childComp in designer.AssociatedComponents) + { + if (childComp.Site != null) + { + list.Add(childComp); + GetAssociatedComponents(childComp, host, list); + } + } + } + + /// + /// Used to retrieve the current location of the given component. + /// + private Point GetLocation(IComponent comp) + { + PropertyDescriptor prop = GetProperty(comp, "Location"); + if (prop != null) + { + try + { + return (Point)prop.GetValue(comp); + } + catch (Exception e) + { + Debug.Fail("Commands may be disabled, the location property was not accessible", e.ToString()); + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + } + } + return Point.Empty; + } + + /// + /// Retrieves the given property on the given component. + /// + protected PropertyDescriptor GetProperty(object comp, string propName) + { + return TypeDescriptor.GetProperties(comp)[propName]; + } + + /// + /// Retrieves the requested service. + /// + protected virtual object GetService(Type serviceType) + { + if (site != null) + { + return site.GetService(serviceType); + } + return null; + } + + /// + /// Used to retrieve the current size of the given component. + /// + private Size GetSize(IComponent comp) + { + PropertyDescriptor prop = GetProperty(comp, "Size"); + if (prop != null) + { + return (Size)prop.GetValue(comp); + } + return Size.Empty; + } + + /// + /// Retrieves the snap information for the given component. + /// + protected virtual void GetSnapInformation(IDesignerHost host, IComponent component, out Size snapSize, out IComponent snapComponent, out PropertyDescriptor snapProperty) + { + // This implementation is shared by all. It just looks for snap properties on the base component. + IComponent currentSnapComponent = null; + PropertyDescriptor gridSizeProp = null; + PropertyDescriptor currentSnapProp = null; + PropertyDescriptorCollection props; + + currentSnapComponent = host.RootComponent; + props = TypeDescriptor.GetProperties(currentSnapComponent); + currentSnapProp = props["SnapToGrid"]; + if (currentSnapProp != null && currentSnapProp.PropertyType != typeof(bool)) + { + currentSnapProp = null; + } + + gridSizeProp = props["GridSize"]; + if (gridSizeProp != null && gridSizeProp.PropertyType != typeof(Size)) + { + gridSizeProp = null; + } + + // Finally, now that we've got the various properties and components, dole out the values. + snapComponent = currentSnapComponent; + snapProperty = currentSnapProp; + if (gridSizeProp != null) + { + snapSize = (Size)gridSizeProp.GetValue(snapComponent); + } + else + { + snapSize = Size.Empty; + } + } + + /// + /// Called before doing any change to multiple controls to check if we have the right to make any change otherwise we would get a checkout message for each control we call setvalue on + /// + protected bool CanCheckout(IComponent comp) + { + IComponentChangeService changeSvc = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + if (changeSvc != null) + { + try + { + changeSvc.OnComponentChanging(comp, null); + } + catch (CheckoutException chkex) + { + if (chkex == CheckoutException.Canceled) + return false; + throw chkex; + } + } + return true; + } + + /// + /// Called by the event handler service when the current event handler has changed. Here we invalidate all of our menu items so that they can pick up the new event handler. + /// + private void OnEventHandlerChanged(object sender, EventArgs e) + { + OnUpdateCommandStatus(); + } + + /// + /// Called for the two cancel commands we support. + /// + private void OnKeyCancel(object sender, EventArgs e) + { + OnKeyCancel(sender); + } + + /// + /// Called for the two cancel commands we support. Returns true If we did anything with the cancel, or false if not. + /// + protected virtual bool OnKeyCancel(object sender) + { + bool handled = false; + // The base implementation here just checks to see if we are dragging. If we are, then we abort the drag. + if (BehaviorService != null && BehaviorService.HasCapture) + { + BehaviorService.OnLoseCapture(); + handled = true; + } + else + { + IToolboxService tbx = (IToolboxService)GetService(typeof(IToolboxService)); + if (tbx != null && tbx.GetSelectedToolboxItem((IDesignerHost)GetService(typeof(IDesignerHost))) != null) + { + tbx.SelectedToolboxItemUsed(); + NativeMethods.POINT p = new NativeMethods.POINT(); + NativeMethods.GetCursorPos(p); + IntPtr hwnd = NativeMethods.WindowFromPoint(p.x, p.y); + if (hwnd != IntPtr.Zero) + { + NativeMethods.SendMessage(hwnd, NativeMethods.WM_SETCURSOR, hwnd, (IntPtr)NativeMethods.HTCLIENT); + } + else + { + Cursor.Current = Cursors.Default; + } + handled = true; + } + } + return handled; + } + + /// + /// Called for the "default" command, typically the Enter key. + /// + protected void OnKeyDefault(object sender, EventArgs e) + { + // Return key. Handle it like a double-click on the primary selection + ISelectionService selSvc = SelectionService; + if (selSvc != null) + { + if (selSvc.PrimarySelection is IComponent pri) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + IDesigner designer = host.GetDesigner(pri); + + if (designer != null) + { + designer.DoDefaultAction(); + } + } + } + } + } + + /// + /// Called for all cursor movement commands. + /// + protected virtual void OnKeyMove(object sender, EventArgs e) + { + // Arrow keys. Begin a drag if the selection isn't locked. + ISelectionService selSvc = SelectionService; + if (selSvc != null) + { + if (selSvc.PrimarySelection is IComponent comp) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + PropertyDescriptor lockedProp = TypeDescriptor.GetProperties(comp)["Locked"]; + if (lockedProp == null || (lockedProp.PropertyType == typeof(bool) && ((bool)lockedProp.GetValue(comp))) == false) + { + CommandID cmd = ((MenuCommand)sender).CommandID; + bool invertSnap = false; + int moveOffsetX = 0; + int moveOffsetY = 0; + + if (cmd.Equals(MenuCommands.KeyMoveUp)) + { + moveOffsetY = -1; + } + else if (cmd.Equals(MenuCommands.KeyMoveDown)) + { + moveOffsetY = 1; + } + else if (cmd.Equals(MenuCommands.KeyMoveLeft)) + { + moveOffsetX = -1; + } + else if (cmd.Equals(MenuCommands.KeyMoveRight)) + { + moveOffsetX = 1; + } + else if (cmd.Equals(MenuCommands.KeyNudgeUp)) + { + moveOffsetY = -1; + invertSnap = true; + } + else if (cmd.Equals(MenuCommands.KeyNudgeDown)) + { + moveOffsetY = 1; + invertSnap = true; + } + else if (cmd.Equals(MenuCommands.KeyNudgeLeft)) + { + moveOffsetX = -1; + invertSnap = true; + } + else if (cmd.Equals(MenuCommands.KeyNudgeRight)) + { + moveOffsetX = 1; + invertSnap = true; + } + else + { + Debug.Fail("Unknown command mapped to OnKeyMove: " + cmd.ToString()); + } + + DesignerTransaction trans; + if (selSvc.SelectionCount > 1) + { + trans = host.CreateTransaction(string.Format(SR.DragDropMoveComponents, selSvc.SelectionCount)); + } + else + { + trans = host.CreateTransaction(string.Format(SR.DragDropMoveComponent, comp.Site.Name)); + } + + try + { + //if we can find the behaviorservice, then we can use it and the SnapLineEngine to help us move these controls... + if (BehaviorService != null) + { + Control primaryControl = comp as Control; //this can be null (when we are moving a component in the ComponenTray) + bool useSnapLines = BehaviorService.UseSnapLines; + // If we have previous snaplines, we always want to erase them, no matter what. VS Whidbey #397709 + if (dragManager != null) + { + EndDragManager(); + } + + //If we CTRL+Arrow and we're using SnapLines - snap to the next location + //Don't snap if we are moving a component in the ComponentTray + if (invertSnap && useSnapLines && primaryControl != null) + { + ArrayList selComps = new ArrayList(selSvc.GetSelectedComponents()); + //create our snapline engine + dragManager = new DragAssistanceManager(comp.Site, selComps); + //ask our snapline engine to find the nearest snap position with the given direction + Point snappedOffset = dragManager.OffsetToNearestSnapLocation(primaryControl, new Point(moveOffsetX, moveOffsetY)); + + //update the offset according to the snapline engine + // This is the offset assuming origin is in the upper-left. + moveOffsetX = snappedOffset.X; + moveOffsetY = snappedOffset.Y; + + // If the parent is mirrored then we need to negate moveOffsetX. This is because moveOffsetX assumes that the origin is upper left. That is, when moveOffsetX is positive, we are moving right, negative when moving left. + // The parent container's origin depends on its mirroring property. Thus when we call propLoc.setValue below, we need to make sure that our moveOffset.X correctly reflects the placement of the parent container's origin. + // We need to do this AFTER we calculate the snappedOffset. This is because the dragManager calculations are all based on an origin in the upper-left. + if (primaryControl.Parent.IsMirrored) + { + moveOffsetX *= -1; + } + } + //if we used a regular arrow key and we're in SnapToGrid mode... + else if (!invertSnap && !useSnapLines) + { + bool snapOn = false; + Size snapSize = Size.Empty; + + GetSnapInformation(host, comp, out snapSize, out IComponent snapComponent, out PropertyDescriptor snapProperty); + if (snapProperty != null) + { + snapOn = (bool)snapProperty.GetValue(snapComponent); + } + + if (snapOn && !snapSize.IsEmpty) + { + moveOffsetX *= snapSize.Width; + moveOffsetY *= snapSize.Height; + if (primaryControl != null) + { + //ask the parent to adjust our wanna-be snapped position + if (host.GetDesigner(primaryControl.Parent) is ParentControlDesigner parentDesigner) + { + Point loc = primaryControl.Location; + // If the parent is mirrored then we need to negate moveOffsetX. This is because moveOffsetX assumes that the origin is upper left. That is, when moveOffsetX is positive, we are moving right, negative when moving left. + // The parent container's origin depends on its mirroring property. Thus when we call propLoc.setValue below, we need to make sure that our moveOffset.X correctly reflects the placement of the parent container's origin. + // Should do this BEFORE we get the snapped point. + if (primaryControl.Parent.IsMirrored) + { + moveOffsetX *= -1; + } + loc.Offset(moveOffsetX, moveOffsetY); + + loc = parentDesigner.GetSnappedPoint(loc); + + //reset our offsets now that we've snapped correctly + if (moveOffsetX != 0) + { + moveOffsetX = loc.X - primaryControl.Location.X; + } + if (moveOffsetY != 0) + { + moveOffsetY = loc.Y - primaryControl.Location.Y; + } + } + + } + } + else + { + // In this case we are just going to move 1 pixel, so let's adjust for Mirroring + if (primaryControl != null && primaryControl.Parent.IsMirrored) + { + moveOffsetX *= -1; + } + } + } + else + { + if (primaryControl != null && primaryControl.Parent.IsMirrored) + { + moveOffsetX *= -1; + } + } + + SelectionRules rules = SelectionRules.Moveable | SelectionRules.Visible; + foreach (IComponent component in selSvc.GetSelectedComponents()) + { + if (host.GetDesigner(component) is ControlDesigner des && ((des.SelectionRules & rules) != rules)) + { + //the control must match the rules, if not, then we don't move it + continue; + } + + // Components are always moveable and visible + PropertyDescriptor propLoc = TypeDescriptor.GetProperties(component)["Location"]; + if (propLoc != null) + { + Point loc = (Point)propLoc.GetValue(component); + loc.Offset(moveOffsetX, moveOffsetY); + propLoc.SetValue(component, loc); + } + + //change the Status information .... + if (component == selSvc.PrimarySelection && _statusCommandUI != null) + { + _statusCommandUI.SetStatusInformation(component as Component); + } + } + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + + if (dragManager != null) + { + //start our timer for the snaplines + SnapLineTimer.Start(); + + //render any lines + dragManager.RenderSnapLinesInternal(); + } + } + } + } + } + } + } + + /// + /// Called for all alignment operations that key off of a primary selection. + /// + protected void OnMenuAlignByPrimary(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + CommandID id = cmd.CommandID; + + //Need to get the location for the primary control, we do this here (instead of onselectionchange) because the control could be dragged around once it is selected and might have a new location + Point primaryLocation = GetLocation(primarySelection); + Size primarySize = GetSize(primarySelection); + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + // Now loop through each of the components. + ICollection comps = SelectionService.GetSelectedComponents(); + // Inform the designer that we are about to monkey with a ton of properties. + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + DesignerTransaction trans = null; + try + { + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.CommandSetAlignByPrimary, comps.Count)); + } + + bool firstTry = true; + Point loc = Point.Empty; + foreach (object obj in comps) + { + if (obj == primarySelection) + { + continue; + } + + IComponent comp = obj as IComponent; + if (comp != null && host != null) + { + if (!(host.GetDesigner(comp) is ControlDesigner des)) + { + continue; + } + } + + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(comp); +PropertyDescriptor locProp = props["Location"]; + PropertyDescriptor sizeProp = props["Size"]; + PropertyDescriptor lockProp = props["Locked"]; + // Skip all components that are locked + if (lockProp != null) + { + if ((bool)lockProp.GetValue(comp)) + continue; + } + + // Skip all components that don't have a location property + if (locProp == null || locProp.IsReadOnly) + { + continue; + } + + // Skip all components that don't have size if we're doing a size operation. + if (id.Equals(MenuCommands.AlignBottom) || + id.Equals(MenuCommands.AlignHorizontalCenters) || + id.Equals(MenuCommands.AlignVerticalCenters) || + id.Equals(MenuCommands.AlignRight)) + { + if (sizeProp == null || sizeProp.IsReadOnly) + { + continue; + } + } + + // Align bottom + if (id.Equals(MenuCommands.AlignBottom)) + { + loc = (Point)locProp.GetValue(comp); + Size size = (Size)sizeProp.GetValue(comp); + loc.Y = primaryLocation.Y + primarySize.Height - size.Height; + } + // Align horizontal centers + else if (id.Equals(MenuCommands.AlignHorizontalCenters)) + { + loc = (Point)locProp.GetValue(comp); + Size size = (Size)sizeProp.GetValue(comp); + loc.Y = primarySize.Height / 2 + primaryLocation.Y - size.Height / 2; + } + // Align left + else if (id.Equals(MenuCommands.AlignLeft)) + { + loc = (Point)locProp.GetValue(comp); + loc.X = primaryLocation.X; + } + // Align right + else if (id.Equals(MenuCommands.AlignRight)) + { + loc = (Point)locProp.GetValue(comp); + Size size = (Size)sizeProp.GetValue(comp); + loc.X = primaryLocation.X + primarySize.Width - size.Width; + } + // Align top + else if (id.Equals(MenuCommands.AlignTop)) + { + loc = (Point)locProp.GetValue(comp); + loc.Y = primaryLocation.Y; + } + // Align vertical centers + else if (id.Equals(MenuCommands.AlignVerticalCenters)) + { + loc = (Point)locProp.GetValue(comp); + Size size = (Size)sizeProp.GetValue(comp); + loc.X = primarySize.Width / 2 + primaryLocation.X - size.Width / 2; + } + else + { + Debug.Fail("Unrecognized command: " + id.ToString()); + } + + if (firstTry && !CanCheckout(comp)) + { + return; + } + firstTry = false; + + + locProp.SetValue(comp, loc); + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the align->to grid menu item is selected. + /// + protected void OnMenuAlignToGrid(object sender, EventArgs e) + { + Size gridSize = Size.Empty; + PropertyDescriptor locProp = null; + PropertyDescriptor lockedProp = null; + Point loc = Point.Empty; + int delta; + + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + ICollection selectedComponents = SelectionService.GetSelectedComponents(); + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + DesignerTransaction trans = null; + + try + { + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.CommandSetAlignToGrid, selectedComponents.Count)); + if (host.RootComponent is Control baseComponent) + { + PropertyDescriptor prop = GetProperty(baseComponent, "GridSize"); + if (prop != null) + { + gridSize = (Size)prop.GetValue(baseComponent); + } + if (prop == null || gridSize.IsEmpty) + { + //bail silently here + return; + } + } + + } + bool firstTry = true; + // for each component, we round to the nearest snap offset for x and y + foreach (object comp in selectedComponents) + { + // first check to see if the component is locked, if so - don't move it... + lockedProp = GetProperty(comp, "Locked"); + if (lockedProp != null && ((bool)lockedProp.GetValue(comp)) == true) + { + continue; + } + + // if the designer for this component isn't a ControlDesigner (maybe it's something in the component tray) then don't try to align it to grid. + IComponent component = comp as IComponent; + if (component != null && host != null) + { + if (!(host.GetDesigner(component) is ControlDesigner des)) + { + continue; + } + } + + // get the location property + locProp = GetProperty(comp, "Location"); + // get the current value + if (locProp == null || locProp.IsReadOnly) + { + continue; + } + loc = (Point)locProp.GetValue(comp); + + // round the x to the snap size + delta = loc.X % gridSize.Width; + if (delta < (gridSize.Width / 2)) + { + loc.X -= delta; + } + else + { + loc.X += (gridSize.Width - delta); + } + + // round the y to the gridsize + delta = loc.Y % gridSize.Height; + if (delta < (gridSize.Height / 2)) + { + loc.Y -= delta; + } + else + { + loc.Y += (gridSize.Height - delta); + } + + // look if it's ok to change + if (firstTry && !CanCheckout(component)) + { + return; + } + firstTry = false; + + // set the value + locProp.SetValue(comp, loc); + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the center horizontally or center vertically menu item is selected. + /// + protected void OnMenuCenterSelection(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + CommandID cmdID = cmd.CommandID; + + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + // NOTE: this only works on Control types + ICollection selectedComponents = SelectionService.GetSelectedComponents(); + Control viewParent = null; + Size size = Size.Empty; + Point loc = Point.Empty; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + DesignerTransaction trans = null; + try + { + if (host != null) + { + string batchString; + if (cmdID == MenuCommands.CenterHorizontally) + { + batchString = string.Format(SR.WindowsFormsCommandCenterX, selectedComponents.Count); + } + else + { + batchString = string.Format(SR.WindowsFormsCommandCenterY, selectedComponents.Count); + } + trans = host.CreateTransaction(batchString); + } + + //subhag calculate the union REctangle : ASURT 67753 + int top = Int32.MaxValue; + int left = Int32.MaxValue; + int right = Int32.MinValue; + int bottom = Int32.MinValue; + + foreach (object obj in selectedComponents) + { + if (obj is Control) + { + IComponent comp = (IComponent)obj; + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(comp); + PropertyDescriptor locProp = props["Location"]; + PropertyDescriptor sizeProp = props["Size"]; + // Skip all components that don't have location and size properties + if (locProp == null || sizeProp == null || locProp.IsReadOnly || sizeProp.IsReadOnly) + { + continue; + } + + // Also, skip all locked componenents... + PropertyDescriptor lockProp = props["Locked"]; + if (lockProp != null && (bool)lockProp.GetValue(comp) == true) + { + continue; + } + + size = (Size)sizeProp.GetValue(comp); + loc = (Point)locProp.GetValue(comp); + //cache the first parent we see - if there's a mix of different parents - we'll just center based on the first one + if (viewParent == null) + { + viewParent = ((Control)comp).Parent; + } + + if (loc.X < left) + left = loc.X; + if (loc.Y < top) + top = loc.Y; + if (loc.X + size.Width > right) + right = loc.X + size.Width; + if (loc.Y + size.Height > bottom) + bottom = loc.Y + size.Height; + } + } + + //if we never found a viewParent (some read-only inherited scenarios then simply bail + if (viewParent == null) + { + return; + } + + int centerOfUnionRectX = (left + right) / 2; + int centerOfUnionRectY = (top + bottom) / 2; + + int centerOfParentX = (viewParent.ClientSize.Width) / 2; + int centerOfParentY = (viewParent.ClientSize.Height) / 2; + + int deltaX = 0; + int deltaY = 0; + + bool shiftRight = false; + bool shiftBottom = false; + + if (centerOfParentX >= centerOfUnionRectX) + { + deltaX = centerOfParentX - centerOfUnionRectX; + shiftRight = true; + } + else + deltaX = centerOfUnionRectX - centerOfParentX; + + if (centerOfParentY >= centerOfUnionRectY) + { + deltaY = centerOfParentY - centerOfUnionRectY; + shiftBottom = true; + } + else + deltaY = centerOfUnionRectY - centerOfParentY; + + bool firstTry = true; + foreach (object obj in selectedComponents) + { + if (obj is Control) + { + IComponent comp = (IComponent)obj; + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(comp); + PropertyDescriptor locProp = props["Location"]; + if (locProp.IsReadOnly) + { + continue; + } + + loc = (Point)locProp.GetValue(comp); + + if (cmdID == MenuCommands.CenterHorizontally) + { + if (shiftRight) + loc.X += deltaX; + else + loc.X -= deltaX; + } + else if (cmdID == MenuCommands.CenterVertically) + { + if (shiftBottom) + loc.Y += deltaY; + else + loc.Y -= deltaY; + } + // look if it's ok to change the first time + if (firstTry && !CanCheckout(comp)) + { + return; + } + firstTry = false; + // do the change + locProp.SetValue(comp, loc); + } + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the copy menu item is selected. + /// + protected void OnMenuCopy(object sender, EventArgs e) + { + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + ICollection selectedComponents = GetCopySelection(); + selectedComponents = PrependComponentNames(selectedComponents); + + IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService)); + Debug.Assert(ds != null, "No designer serialization service -- we cannot copy to clipboard"); + if (ds != null) + { + object serializationData = ds.Serialize(selectedComponents); + MemoryStream stream = new MemoryStream(); + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Serialize(stream, serializationData); + stream.Seek(0, SeekOrigin.Begin); + byte[] bytes = stream.GetBuffer(); + IDataObject dataObj = new DataObject(CF_DESIGNER, bytes); + Clipboard.SetDataObject(dataObj); + } + UpdateClipboardItems(null, null); + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the cut menu item is selected. + /// + protected void OnMenuCut(object sender, EventArgs e) + { + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + ICollection selectedComponents = GetCopySelection(); + int cutCount = selectedComponents.Count; + selectedComponents = PrependComponentNames(selectedComponents); + IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService)); + Debug.Assert(ds != null, "No designer serialization service -- we cannot copy to clipboard"); + if (ds != null) + { + object serializationData = ds.Serialize(selectedComponents); + MemoryStream stream = new MemoryStream(); + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Serialize(stream, serializationData); + stream.Seek(0, SeekOrigin.Begin); + byte[] bytes = stream.GetBuffer(); + IDataObject dataObj = new DataObject(CF_DESIGNER, bytes); + Clipboard.SetDataObject(dataObj); + + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Control commonParent = null; + + if (host != null) + { + IComponentChangeService changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + DesignerTransaction trans = null; + + ArrayList designerList = new ArrayList(); + try + { + trans = host.CreateTransaction(string.Format(SR.CommandSetCutMultiple, cutCount)); + // clear the selected components so we aren't browsing them + SelectionService.SetSelectedComponents(new object[0], SelectionTypes.Replace); + object[] selComps = new object[selectedComponents.Count]; + selectedComponents.CopyTo(selComps, 0); + + for (int i = 0; i < selComps.Length; i++) + { + object obj = selComps[i]; + // We should never delete the base component. + if (obj == host.RootComponent || !(obj is IComponent component)) + { + continue; + } + //Perf: We suspend Component Changing Events on parent for bulk changes to avoid unnecessary serialization\deserialization for undo see bug 488115 + if (obj is Control c) + { + Control parent = c.Parent; + if (parent != null) + { + if (host.GetDesigner(parent) is ParentControlDesigner designer && !designerList.Contains(designer)) + { + designer.SuspendChangingEvents(); + designerList.Add(designer); + designer.ForceComponentChanging(); + } + } + } + } + // go backward so we destroy parents before children + + for (int i = 0; i < selComps.Length; i++) + { + object obj = selComps[i]; + // We should never delete the base component. + // + if (obj == host.RootComponent || !(obj is IComponent component)) + { + continue; + } + + Control c = obj as Control; + //Cannot use idx = 1 to check (see diff) due to the call to PrependComponentNames, which adds non IComponent objects to the beginning of selectedComponents. Thus when we finally get here idx would be > 1. + if (commonParent == null && c != null) + { + commonParent = c.Parent; + } + else if (commonParent != null && c != null) + { + Control selectedControl = c; + if (selectedControl.Parent != commonParent && !commonParent.Contains(selectedControl)) + { + // look for internal parenting + if (selectedControl == commonParent || selectedControl.Contains(commonParent)) + { + commonParent = selectedControl.Parent; + } + else + { + commonParent = null; + } + } + } + + + if (component != null) + { + ArrayList al = new ArrayList(); + GetAssociatedComponents(component, host, al); + foreach (IComponent comp in al) + { + changeService.OnComponentChanging(comp, null); + } + host.DestroyComponent(component); + } + } + } + finally + { + if (trans != null) + trans.Commit(); + foreach (ParentControlDesigner des in designerList) + { + if (des != null) + { + des.ResumeChangingEvents(); + } + } + + } + + if (commonParent != null) + { + SelectionService.SetSelectedComponents(new object[] { commonParent }, SelectionTypes.Replace); + } + else if (SelectionService.PrimarySelection == null) + { + SelectionService.SetSelectedComponents(new object[] { host.RootComponent }, SelectionTypes.Replace); + } + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the delete menu item is selected. + /// + protected void OnMenuDelete(object sender, EventArgs e) + { + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + if (site != null) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + + if (SelectionService == null) + { + return; + } + + if (host != null) + { + IComponentChangeService changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + ICollection comps = SelectionService.GetSelectedComponents(); + string desc = string.Format(SR.CommandSetDelete, comps.Count); + DesignerTransaction trans = null; + IComponent commonParent = null; + bool commonParentSet = false; + ArrayList designerList = new ArrayList(); + try + { + trans = host.CreateTransaction(desc); + SelectionService.SetSelectedComponents(new object[0], SelectionTypes.Replace); + foreach (object obj in comps) + { + if (!(obj is IComponent comp) || comp.Site == null) + { + continue; + } + //Perf: We suspend Component Changing Events on parent for bulk changes to avoid unnecessary serialization\deserialization for undo + if (obj is Control c) + { + Control parent = c.Parent; + if (parent != null) + { + if (host.GetDesigner(parent) is ParentControlDesigner designer && !designerList.Contains(designer)) + { + designer.SuspendChangingEvents(); + designerList.Add(designer); + designer.ForceComponentChanging(); + } + } + } + } + foreach (object obj in comps) + { + // If it's not a component, we can't delete it. It also may have already been deleted as part of a parent operation, so we skip it. + if (!(obj is IComponent c) || c.Site == null) + { + continue; + } + + // We should never delete the base component. + if (obj == host.RootComponent) + { + continue; + } + + Control control = obj as Control; + if (!commonParentSet) + { + if (control != null) + { + commonParent = control.Parent; + } + else + { + // if this is not a Control, see if we can get an ITreeDesigner from it, and figure out the Component from that. + if (host.GetDesigner((IComponent)obj) is ITreeDesigner designer) + { + IDesigner parentDesigner = designer.Parent; + if (parentDesigner != null) + { + commonParent = parentDesigner.Component; + } + } + } + commonParentSet = (commonParent != null); + } + else if (commonParent != null) + { + if (control != null && commonParent is Control) + { + Control selectedControl = control; + Control controlCommonParent = (Control)commonParent; + if (selectedControl.Parent != controlCommonParent && !controlCommonParent.Contains(selectedControl)) + { + // look for internal parenting + if (selectedControl == controlCommonParent || selectedControl.Contains(controlCommonParent)) + { + commonParent = selectedControl.Parent; + } + else + { + // start walking up until we find a common parent + while (controlCommonParent != null && !controlCommonParent.Contains(selectedControl)) + { + controlCommonParent = controlCommonParent.Parent; + } + commonParent = controlCommonParent; + } + } + } + else + { + // for these we aren't as thorough as we are with the Control-based ones. we just walk up the chain until we find that parent or the root component. + if (host.GetDesigner((IComponent)obj) is ITreeDesigner designer && host.GetDesigner(commonParent) is ITreeDesigner commonParentDesigner && designer.Parent != commonParentDesigner) + { + ArrayList designerChain = new ArrayList(); + ArrayList parentDesignerChain = new ArrayList(); + // walk the chain of designers from the current parent designer up to the root component, and for the current component designer. + for (designer = designer.Parent as ITreeDesigner; + designer != null; + designer = designer.Parent as ITreeDesigner) + { + designerChain.Add(designer); + } + + for (commonParentDesigner = commonParentDesigner.Parent as ITreeDesigner; + commonParentDesigner != null; + commonParentDesigner = commonParentDesigner.Parent as ITreeDesigner) + { + parentDesignerChain.Add(commonParentDesigner); + } + + // now that we've got the trees built up, start comparing them from the ends to see where they diverge. + ArrayList shorterList = designerChain.Count < parentDesignerChain.Count ? designerChain : parentDesignerChain; + ArrayList longerList = (shorterList == designerChain ? parentDesignerChain : designerChain); + commonParentDesigner = null; + if (shorterList.Count > 0 && longerList.Count > 0) + { + int shortIndex = Math.Max(0, shorterList.Count - 1); + int longIndex = Math.Max(0, longerList.Count - 1); + while (shortIndex >= 0 && longIndex >= 0) + { + if (shorterList[shortIndex] != longerList[longIndex]) + { + break; + } + commonParentDesigner = (ITreeDesigner)shorterList[shortIndex]; + shortIndex--; + longIndex--; + } + } + if (commonParentDesigner != null) + { + commonParent = commonParentDesigner.Component; + } + else + { + commonParent = null; + } + } + } + + } + + ArrayList al = new ArrayList(); + GetAssociatedComponents((IComponent)obj, host, al); + foreach (IComponent comp in al) + { + changeService.OnComponentChanging(comp, null); + } + host.DestroyComponent((IComponent)obj); + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + + foreach (ParentControlDesigner des in designerList) + { + if (des != null) + { + des.ResumeChangingEvents(); + } + } + } + + + if (commonParent != null && SelectionService.PrimarySelection == null) + { + if (host.GetDesigner(commonParent) is ITreeDesigner commonParentDesigner && commonParentDesigner.Children != null) + { + // choose the first child of the common parent if it has any. + foreach (IDesigner designer in commonParentDesigner.Children) + { + IComponent component = designer.Component; + if (component.Site != null) + { + commonParent = component; + break; + } + } + } + else if (commonParent is Control controlCommonParent) + { + // if we have a common parent, select it's first child + if (controlCommonParent.Controls.Count > 0) + { + controlCommonParent = controlCommonParent.Controls[0]; + while (controlCommonParent != null && controlCommonParent.Site == null) + { + controlCommonParent = controlCommonParent.Parent; + } + commonParent = controlCommonParent; + } + } + if (commonParent != null) + { + SelectionService.SetSelectedComponents(new object[] { commonParent }, SelectionTypes.Replace); + } + else + { + SelectionService.SetSelectedComponents(new object[] { host.RootComponent }, SelectionTypes.Replace); + } + } + else + { + if (SelectionService.PrimarySelection == null) + { + SelectionService.SetSelectedComponents(new object[] { host.RootComponent }, SelectionTypes.Replace); + } + } + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the paste menu item is selected. + /// + + [SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals")] + protected void OnMenuPaste(object sender, EventArgs e) + { + Cursor oldCursor = Cursor.Current; + ArrayList designerList = new ArrayList(); + try + { + Cursor.Current = Cursors.WaitCursor; + // If a control fails to get pasted; then we should remember its associatedComponents so that they are not pasted. + ICollection associatedCompsOfFailedContol = null; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host == null) + return; + + IDataObject dataObj = Clipboard.GetDataObject(); + ICollection components = null; + bool createdItems = false; + ComponentTray tray = null; + int numberOfOriginalTrayControls = 0; + // Get the current number of controls in the Component Tray in the target + tray = GetService(typeof(ComponentTray)) as ComponentTray; + numberOfOriginalTrayControls = tray != null ? tray.Controls.Count : 0; + + // We understand two things: CF_DESIGNER, and toolbox items. + object data = dataObj.GetData(CF_DESIGNER); + using (DesignerTransaction trans = host.CreateTransaction(SR.CommandSetPaste)) + { + if (data is byte[] bytes) + { + MemoryStream s = new MemoryStream(bytes); + if (s != null) + { + // CF_DESIGNER was put on the clipboard by us using the designer serialization service. + IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService)); + if (ds != null) + { + BinaryFormatter formatter = new BinaryFormatter(); + s.Seek(0, SeekOrigin.Begin); + object serializationData = formatter.Deserialize(s); + components = ds.Deserialize(serializationData); + } + } + } + else + { + // Now check for a toolbox item. + IToolboxService ts = (IToolboxService)GetService(typeof(IToolboxService)); + if (ts != null && ts.IsSupported(dataObj, host)) + { + ToolboxItem ti = ts.DeserializeToolboxItem(dataObj, host); + if (ti != null) + { + components = ti.CreateComponents(host); + createdItems = true; + } + } + } + + // Now, if we got some components, hook 'em up! + if (components != null && components.Count > 0) + { + IComponent curComp; + string name; + + //Make copy of Items in Array.. + object[] allComponents = new object[components.Count]; + components.CopyTo(allComponents, 0); + + ArrayList selectComps = new ArrayList(); + ArrayList controls = new ArrayList(); + string[] componentNames = null; + int idx = 0; + + // if the selected item is a frame designer, add to that, otherwise add to the form + IComponent selectedComponent = null; + IDesigner designer = null; + bool dragClient = false; + + IComponent baseComponent = host.RootComponent; + selectedComponent = (IComponent)SelectionService.PrimarySelection; + + if (selectedComponent == null) + { + selectedComponent = baseComponent; + } + + dragClient = false; + ITreeDesigner tree = host.GetDesigner(selectedComponent) as ITreeDesigner; + + while (!dragClient && tree != null) + { + if (tree is IOleDragClient) + { + designer = tree; + dragClient = true; + } + else + { + if (tree == tree.Parent) + break; + tree = tree.Parent as ITreeDesigner; + } + } + + foreach (object obj in components) + { + name = null; + curComp = obj as IComponent; + + // see if we can fish out the original name. When we serialized, we serialized an array of names at the head of the list. This array matches the components that were created. + if (obj is IComponent) + { + if (componentNames != null && idx < componentNames.Length) + { + name = componentNames[idx++]; + } + } + else + { + if (componentNames == null && obj is string[] sa) + { + componentNames = sa; + idx = 0; + continue; + } + } + + if (GetService(typeof(IEventBindingService)) is IEventBindingService evs) + { + PropertyDescriptorCollection eventProps = evs.GetEventProperties(TypeDescriptor.GetEvents(curComp)); + foreach (PropertyDescriptor pd in eventProps) + { + // If we couldn't find a property for this event, or of the property is read only, then abort. + if (pd == null || pd.IsReadOnly) + { + continue; + } + if (pd.GetValue(curComp) is string handler) + { + pd.SetValue(curComp, null); + } + } + } + + if (dragClient) + { + bool foundAssociatedControl = false; + // If we have failed to add a control in this Paste operation ... + if (associatedCompsOfFailedContol != null) + { + // then dont add its children controls. + foreach (Component comp in associatedCompsOfFailedContol) + { + if (comp == obj as Component) + { + foundAssociatedControl = true; + break; + } + } + } + + if (foundAssociatedControl) + { + continue; //continue from here so that we dont add the associted compoenet of a control that failed paste operation. + } + + ICollection designerComps = null; + if (!(host.GetDesigner(curComp) is ComponentDesigner cDesigner)) + { + continue; + } + //store associatedComponents. + designerComps = cDesigner.AssociatedComponents; + ComponentDesigner parentCompDesigner = ((ITreeDesigner)cDesigner).Parent as ComponentDesigner; + Component parentComp = null; + if (parentCompDesigner != null) + { + parentComp = parentCompDesigner.Component as Component; + } + + ArrayList associatedComps = new ArrayList(); + if (parentComp != null) + { + if (parentCompDesigner != null) + { + foreach (IComponent childComp in parentCompDesigner.AssociatedComponents) + { + associatedComps.Add(childComp as Component); + } + } + } + + if (parentComp == null || !(associatedComps.Contains(curComp))) + { + if (parentComp != null) + { + if (host.GetDesigner(parentComp) is ParentControlDesigner parentDesigner && !designerList.Contains(parentDesigner)) + { + parentDesigner.SuspendChangingEvents(); + designerList.Add(parentDesigner); + parentDesigner.ForceComponentChanging(); + } + } + + if (!((IOleDragClient)designer).AddComponent(curComp, name, createdItems)) + { + //cache the associatedComponents only for FAILED control. + associatedCompsOfFailedContol = designerComps; + // now we will jump out of the using block and call trans.Dispose() which in turn calls trans.Cancel for an uncommited transaction, We want to cancel the transaction because otherwise we'll have un-parented controls + return; + } + + Control designerControl = ((IOleDragClient)designer).GetControlForComponent(curComp); + if (designerControl != null) + { + controls.Add(designerControl); + } + // Select the newly Added top level component + if ((TypeDescriptor.GetAttributes(curComp).Contains(DesignTimeVisibleAttribute.Yes)) || curComp is ToolStripItem) + { + selectComps.Add(curComp); + } + + } + // if Parent is not selected... select the curcomp. + else if (associatedComps.Contains(curComp) && Array.IndexOf(allComponents, parentComp) == -1) + { + selectComps.Add(curComp); + } + + bool changeName = false; + + if (curComp is Control c) + { + // if the text is the same as the name, remember it. After we add the control, we'll update the text with the new name. + if (name != null && name.Equals(c.Text)) + { + changeName = true; + } + } + + if (changeName) + { + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(curComp); + PropertyDescriptor nameProp = props["Name"]; + if (nameProp != null && nameProp.PropertyType == typeof(string)) + { + string newName = (string)nameProp.GetValue(curComp); + if (!newName.Equals(name)) + { + PropertyDescriptor textProp = props["Text"]; + if (textProp != null && textProp.PropertyType == nameProp.PropertyType) + { + textProp.SetValue(curComp, nameProp.GetValue(curComp)); + } + } + } + } + } + } + + + // Find those controls that have ControlDesigners and center them on the designer surface + ArrayList compsWithControlDesigners = new ArrayList(); + foreach (Control c in controls) + { + IDesigner des = host.GetDesigner((IComponent)c); + if (des is ControlDesigner) + { + compsWithControlDesigners.Add(c); + } + } + + if (compsWithControlDesigners.Count > 0) + { + // Update the control positions. We want to keep the entire block of controls relative to each other, but relocate them within the container. + UpdatePastePositions(compsWithControlDesigners); + } + + // Figure out if we added components to the component tray, and have the tray adjust their position. MartinTh - removed the old check, since ToolStrips breaks the scenario. ToolStrips have a ControlDesigner, but also add a component to the tray. The old code wouldn't detect that, so the tray location wouldn't get adjusted. Rather than fixing this up in ToolStripKeyboardHandlingService.OnCommandPaste, we do it here, since doing it in the service, wouldn't handle cross-form paste. + if (tray == null) + { + // the paste target did not have a tray already, so let's go get it - if there is one + tray = GetService(typeof(ComponentTray)) as ComponentTray; + } + + if (tray != null) + { + int numberOfTrayControlsAdded = tray.Controls.Count - numberOfOriginalTrayControls; + if (numberOfTrayControlsAdded > 0) + { + ArrayList listOfTrayControls = new ArrayList(); + for (int i = 0; i < numberOfTrayControlsAdded; i++) + { + listOfTrayControls.Add(tray.Controls[numberOfOriginalTrayControls + i]); + } + tray.UpdatePastePositions(listOfTrayControls); + } + } + + // Update the tab indices of all the components. We must first sort the components by their existing tab indices or else we will not preserve their original intent. + controls.Sort(new TabIndexCompare()); + foreach (Control c in controls) + { + UpdatePasteTabIndex(c, c.Parent); + } + + // finally select all the components we added + SelectionService.SetSelectedComponents((object[])selectComps.ToArray(), SelectionTypes.Replace); + + // and bring them to the front - but only if we can mess with the Z-order. + if (designer is ParentControlDesigner parentControlDesigner && parentControlDesigner.AllowSetChildIndexOnDrop) + { + MenuCommand btf = MenuService.FindCommand(MenuCommands.BringToFront); + if (btf != null) + { + btf.Invoke(); + } + } + trans.Commit(); + } + } + } + finally + { + Cursor.Current = oldCursor; + foreach (ParentControlDesigner des in designerList) + { + if (des != null) + { + des.ResumeChangingEvents(); + } + } + } + } + + /// + /// Called when the select all menu item is selected. + /// + protected void OnMenuSelectAll(object sender, EventArgs e) + { + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + if (site != null) + { + Debug.Assert(SelectionService != null, "We need the SelectionService, but we can't find it!"); + if (SelectionService == null) + { + return; + } + + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host != null) + { + ComponentCollection components = host.Container.Components; + object[] selComps; + if (components == null || components.Count == 0) + { + selComps = new IComponent[0]; + } + else + { + selComps = new object[components.Count - 1]; + object baseComp = host.RootComponent; + + int j = 0; + foreach (IComponent comp in components) + { + if (baseComp == comp) + continue; + selComps[j++] = comp; + } + } + SelectionService.SetSelectedComponents(selComps, SelectionTypes.Replace); + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the show grid menu item is selected. + /// + protected void OnMenuShowGrid(object sender, EventArgs e) + { + if (site != null) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host != null) + { + DesignerTransaction trans = null; + try + { + trans = host.CreateTransaction(); + IComponent baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is Control) + { + PropertyDescriptor prop = GetProperty(baseComponent, "DrawGrid"); + if (prop != null) + { + bool drawGrid = (bool)prop.GetValue(baseComponent); + prop.SetValue(baseComponent, !drawGrid); + ((MenuCommand)sender).Checked = !drawGrid; + } + } + } + finally + { + if (trans != null) + trans.Commit(); + } + } + } + } + + /// + /// Handles the various size to commands. + /// + protected void OnMenuSizingCommand(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + CommandID cmdID = cmd.CommandID; + + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + ICollection sel = SelectionService.GetSelectedComponents(); + object[] selectedObjects = new object[sel.Count]; + sel.CopyTo(selectedObjects, 0); + selectedObjects = FilterSelection(selectedObjects, SelectionRules.Visible); + object selPrimary = SelectionService.PrimarySelection; + + Size primarySize = Size.Empty; + Size itemSize = Size.Empty; + PropertyDescriptor sizeProp; + if (selPrimary is IComponent component) + { + sizeProp = GetProperty(component, "Size"); + if (sizeProp == null) + { + //if we couldn't get a valid size for our primary selection, we'll fail silently + return; + } + primarySize = (Size)sizeProp.GetValue(component); + + } + if (selPrimary == null) + { + return; + } + + Debug.Assert(null != selectedObjects, "queryStatus should have disabled this"); + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + DesignerTransaction trans = null; + try + { + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.CommandSetSize, selectedObjects.Length)); + } + + foreach (object obj in selectedObjects) + { + if (obj.Equals(selPrimary)) + continue; + if (!(obj is IComponent comp)) + continue; + + //if the component is locked, no sizing is allowed... + PropertyDescriptor lockedDesc = GetProperty(obj, "Locked"); + if (lockedDesc != null && (bool)lockedDesc.GetValue(obj)) + { + continue; + } + + sizeProp = GetProperty(comp, "Size"); + // Skip all components that don't have a size property + if (sizeProp == null || sizeProp.IsReadOnly) + { + continue; + } + + itemSize = (Size)sizeProp.GetValue(comp); + if (cmdID == MenuCommands.SizeToControlHeight || cmdID == MenuCommands.SizeToControl) + { + itemSize.Height = primarySize.Height; + } + + if (cmdID == MenuCommands.SizeToControlWidth || cmdID == MenuCommands.SizeToControl) + { + itemSize.Width = primarySize.Width; + } + sizeProp.SetValue(comp, itemSize); + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + } + } + finally + { + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the size->to grid menu item is selected. + /// + protected void OnMenuSizeToGrid(object sender, EventArgs e) + { + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + DesignerTransaction trans = null; + try + { + Cursor.Current = Cursors.WaitCursor; + ICollection sel = SelectionService.GetSelectedComponents(); + object[] selectedObjects = new object[sel.Count]; + sel.CopyTo(selectedObjects, 0); + selectedObjects = FilterSelection(selectedObjects, SelectionRules.Visible); + Size size = Size.Empty; + Point loc = Point.Empty; + + Debug.Assert(null != selectedObjects, "queryStatus should have disabled this"); + Size grid = Size.Empty; + PropertyDescriptor sizeProp = null; + PropertyDescriptor locProp = null; + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.CommandSetSizeToGrid, selectedObjects.Length)); + IComponent baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is Control) + { + PropertyDescriptor prop = GetProperty(baseComponent, "CurrentGridSize"); + if (prop != null) + { + grid = (Size)prop.GetValue(baseComponent); + } + } + } + + if (!grid.IsEmpty) + { + foreach (object obj in selectedObjects) + { + IComponent comp = obj as IComponent; + if (obj == null) + { + continue; + } + + sizeProp = GetProperty(comp, "Size"); + locProp = GetProperty(comp, "Location"); + Debug.Assert(sizeProp != null, "No size property on component"); + Debug.Assert(locProp != null, "No location property on component"); + + if (sizeProp == null || locProp == null || sizeProp.IsReadOnly || locProp.IsReadOnly) + { + continue; + } + + size = (Size)sizeProp.GetValue(comp); + loc = (Point)locProp.GetValue(comp); + + size.Width = ((size.Width + (grid.Width / 2)) / grid.Width) * grid.Width; + size.Height = ((size.Height + (grid.Height / 2)) / grid.Height) * grid.Height; + loc.X = (loc.X / grid.Width) * grid.Width; + loc.Y = (loc.Y / grid.Height) * grid.Height; + + sizeProp.SetValue(comp, size); + locProp.SetValue(comp, loc); + } + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the properties menu item is selected on the Context menu + /// + protected void OnMenuDesignerProperties(object sender, EventArgs e) + { + // first, look if the currently selected object has a component editor... + object obj = SelectionService.PrimarySelection; + + if (CheckComponentEditor(obj, true)) + { + return; + } + + IMenuCommandService menuSvc = (IMenuCommandService)GetService(typeof(IMenuCommandService)); + if (menuSvc != null) + { + if (menuSvc.GlobalInvoke(MenuCommands.PropertiesWindow)) + { + return; + } + } + Debug.Assert(false, "Invoking pbrs command failed"); + } + + /// + /// Called when the snap to grid menu item is selected. + /// + protected void OnMenuSnapToGrid(object sender, EventArgs e) + { + if (site != null) + { + IDesignerHost host = (IDesignerHost)site.GetService(typeof(IDesignerHost)); + if (host != null) + { + DesignerTransaction trans = null; + + try + { + trans = host.CreateTransaction(string.Format(SR.CommandSetPaste, 0)); + IComponent baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is Control) + { + PropertyDescriptor prop = GetProperty(baseComponent, "SnapToGrid"); + if (prop != null) + { + bool snapToGrid = (bool)prop.GetValue(baseComponent); + prop.SetValue(baseComponent, !snapToGrid); + ((MenuCommand)sender).Checked = !snapToGrid; + } + } + } + finally + { + if (trans != null) + trans.Commit(); + } + } + } + } + + /// + /// Called when a spacing command is selected + /// + protected void OnMenuSpacingCommand(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + CommandID cmdID = cmd.CommandID; + DesignerTransaction trans = null; + + if (SelectionService == null) + { + return; + } + + Cursor oldCursor = Cursor.Current; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + try + { + Cursor.Current = Cursors.WaitCursor; + // Inform the designer that we are about to monkey with a ton of properties. + Size grid = Size.Empty; + ICollection sel = SelectionService.GetSelectedComponents(); + object[] selectedObjects = new object[sel.Count]; + sel.CopyTo(selectedObjects, 0); + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.CommandSetFormatSpacing, selectedObjects.Length)); + IComponent baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is Control) + { + PropertyDescriptor prop = GetProperty(baseComponent, "CurrentGridSize"); + if (prop != null) + { + grid = (Size)prop.GetValue(baseComponent); + } + } + } + + selectedObjects = FilterSelection(selectedObjects, SelectionRules.Visible); + int nEqualDelta = 0; + Debug.Assert(null != selectedObjects, "queryStatus should have disabled this"); + PropertyDescriptor curSizeDesc = null, lastSizeDesc = null; + PropertyDescriptor curLocDesc = null, lastLocDesc = null; + Size curSize = Size.Empty, lastSize = Size.Empty; + Point curLoc = Point.Empty, lastLoc = Point.Empty; + Point primaryLoc = Point.Empty; + IComponent curComp = null, lastComp = null; + int sort = -1; + + // Must sort differently if we're horizontal or vertical... + if (cmdID == MenuCommands.HorizSpaceConcatenate || + cmdID == MenuCommands.HorizSpaceDecrease || + cmdID == MenuCommands.HorizSpaceIncrease || + cmdID == MenuCommands.HorizSpaceMakeEqual) + { + sort = SORT_HORIZONTAL; + } + else if (cmdID == MenuCommands.VertSpaceConcatenate || + cmdID == MenuCommands.VertSpaceDecrease || + cmdID == MenuCommands.VertSpaceIncrease || + cmdID == MenuCommands.VertSpaceMakeEqual) + { + sort = SORT_VERTICAL; + } + else + { + throw new ArgumentException(SR.CommandSetUnknownSpacingCommand); + } + + SortSelection(selectedObjects, sort); + + //now that we're sorted, lets get our primary selection and it's index + object primary = SelectionService.PrimarySelection; + int primaryIndex = 0; + if (primary != null) + primaryIndex = Array.IndexOf(selectedObjects, primary); + + // And compute delta values for Make Equal + if (cmdID == MenuCommands.HorizSpaceMakeEqual || cmdID == MenuCommands.VertSpaceMakeEqual) + { + int total, n; + + total = 0; + for (n = 0; n < selectedObjects.Length; n++) + { + curSize = Size.Empty; + if (selectedObjects[n] is IComponent component) + { + curComp = component; + curSizeDesc = GetProperty(curComp, "Size"); + if (curSizeDesc != null) + { + curSize = (Size)curSizeDesc.GetValue(curComp); + } + } + + if (sort == SORT_HORIZONTAL) + { + total += curSize.Width; + } + else + { + total += curSize.Height; + } + } + + lastComp = curComp = null; + curSize = Size.Empty; + curLoc = Point.Empty; + for (n = 0; n < selectedObjects.Length; n++) + { + curComp = selectedObjects[n] as IComponent; + if (curComp != null) + { + // only get the descriptors if we've changed component types + if (lastComp == null || curComp.GetType() != lastComp.GetType()) + { + curSizeDesc = GetProperty(curComp, "Size"); + curLocDesc = GetProperty(curComp, "Location"); + } + lastComp = curComp; + + if (curLocDesc != null) + { + curLoc = (Point)curLocDesc.GetValue(curComp); + } + else + { + continue; + } + + if (curSizeDesc != null) + { + curSize = (Size)curSizeDesc.GetValue(curComp); + } + else + { + continue; + } + + if (!curSize.IsEmpty && !curLoc.IsEmpty) + { + break; + } + } + } + + for (n = selectedObjects.Length - 1; n >= 0; n--) + { + curComp = selectedObjects[n] as IComponent; + if (curComp != null) + { + // only get the descriptors if we've changed component types + if (lastComp == null || curComp.GetType() != lastComp.GetType()) + { + curSizeDesc = GetProperty(curComp, "Size"); + curLocDesc = GetProperty(curComp, "Location"); + } + lastComp = curComp; + + if (curLocDesc != null) + { + lastLoc = (Point)curLocDesc.GetValue(curComp); + } + else + { + continue; + } + + if (curSizeDesc != null) + { + lastSize = (Size)curSizeDesc.GetValue(curComp); + } + else + { + continue; + } + + if (curSizeDesc != null && curLocDesc != null) + { + break; + } + } + } + + if (curSizeDesc != null && curLocDesc != null) + { + if (sort == SORT_HORIZONTAL) + { + nEqualDelta = (lastSize.Width + lastLoc.X - curLoc.X - total) / (selectedObjects.Length - 1); + } + else + { + nEqualDelta = (lastSize.Height + lastLoc.Y - curLoc.Y - total) / (selectedObjects.Length - 1); + } + if (nEqualDelta < 0) + nEqualDelta = 0; + } + } + curComp = lastComp = null; + + if (primary != null) + { + PropertyDescriptor primaryLocDesc = GetProperty(primary, "Location"); + if (primaryLocDesc != null) + { + primaryLoc = (Point)primaryLocDesc.GetValue(primary); + } + } + + // Finally move the components + for (int n = 0; n < selectedObjects.Length; n++) + { + curComp = (IComponent)selectedObjects[n]; + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(curComp); + //Check to see if the component we are about to move is locked... + PropertyDescriptor lockedDesc = props["Locked"]; + if (lockedDesc != null && (bool)lockedDesc.GetValue(curComp)) + { + continue; // locked property of our component is true, so don't move it + } + + if (lastComp == null || lastComp.GetType() != curComp.GetType()) + { + curSizeDesc = props["Size"]; + curLocDesc = props["Location"]; + } + else + { + curSizeDesc = lastSizeDesc; + curLocDesc = lastLocDesc; + } + + if (curLocDesc != null) + { + curLoc = (Point)curLocDesc.GetValue(curComp); + } + else + { + continue; + } + + if (curSizeDesc != null) + { + curSize = (Size)curSizeDesc.GetValue(curComp); + } + else + { + continue; + } + + int lastIndex = Math.Max(0, n - 1); + lastComp = (IComponent)selectedObjects[lastIndex]; + if (lastComp.GetType() != curComp.GetType()) + { + lastSizeDesc = GetProperty(lastComp, "Size"); + lastLocDesc = GetProperty(lastComp, "Location"); + } + else + { + lastSizeDesc = curSizeDesc; + lastLocDesc = curLocDesc; + } + + if (lastLocDesc != null) + { + lastLoc = (Point)lastLocDesc.GetValue(lastComp); + } + else + { + continue; + } + + if (lastSizeDesc != null) + { + lastSize = (Size)lastSizeDesc.GetValue(lastComp); + } + else + { + continue; + } + + if (cmdID == MenuCommands.HorizSpaceConcatenate && n > 0) + { + curLoc.X = lastLoc.X + lastSize.Width; + } + else if (cmdID == MenuCommands.HorizSpaceDecrease) + { + if (primaryIndex < n) + { + curLoc.X -= grid.Width * (n - primaryIndex); + if (curLoc.X < primaryLoc.X) + curLoc.X = primaryLoc.X; + } + else if (primaryIndex > n) + { + curLoc.X += grid.Width * (primaryIndex - n); + if (curLoc.X > primaryLoc.X) + curLoc.X = primaryLoc.X; + } + } + else if (cmdID == MenuCommands.HorizSpaceIncrease) + { + if (primaryIndex < n) + { + curLoc.X += grid.Width * (n - primaryIndex); + } + else if (primaryIndex > n) + { + curLoc.X -= grid.Width * (primaryIndex - n); + } + + } + else if (cmdID == MenuCommands.HorizSpaceMakeEqual && n > 0) + { + curLoc.X = lastLoc.X + lastSize.Width + nEqualDelta; + } + else if (cmdID == MenuCommands.VertSpaceConcatenate && n > 0) + { + curLoc.Y = lastLoc.Y + lastSize.Height; + } + else if (cmdID == MenuCommands.VertSpaceDecrease) + { + if (primaryIndex < n) + { + curLoc.Y -= grid.Height * (n - primaryIndex); + if (curLoc.Y < primaryLoc.Y) + curLoc.Y = primaryLoc.Y; + } + else if (primaryIndex > n) + { + curLoc.Y += grid.Height * (primaryIndex - n); + if (curLoc.Y > primaryLoc.Y) + curLoc.Y = primaryLoc.Y; + } + } + else if (cmdID == MenuCommands.VertSpaceIncrease) + { + if (primaryIndex < n) + { + curLoc.Y += grid.Height * (n - primaryIndex); + } + else if (primaryIndex > n) + { + curLoc.Y -= grid.Height * (primaryIndex - n); + } + } + else if (cmdID == MenuCommands.VertSpaceMakeEqual && n > 0) + { + curLoc.Y = lastLoc.Y + lastSize.Height + nEqualDelta; + } + + if (!curLocDesc.IsReadOnly) + { + curLocDesc.SetValue(curComp, curLoc); + } + lastComp = curComp; + } + } + finally + { + if (trans != null) + { + trans.Commit(); + } + Cursor.Current = oldCursor; + } + } + + /// + /// Called when the current selection changes. Here we determine what commands can and can't be enabled. + /// + protected void OnSelectionChanged(object sender, EventArgs e) + { + if (SelectionService == null/*: UNDONE: BehaviorWork || SelectionUIService == null*/) + { + return; + } + + _selectionVersion++; + // Update our cached selection counts. + selCount = SelectionService.SelectionCount; + IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(designerHost != null, "Failed to get designer host"); + // if the base component is selected, we'll say that nothing's selected so we don't get wierd behavior + if (selCount > 0 && designerHost != null) + { + object baseComponent = designerHost.RootComponent; + if (baseComponent != null && SelectionService.GetComponentSelected(baseComponent)) + { + selCount = 0; + } + } + + primarySelection = SelectionService.PrimarySelection as IComponent; + _selectionInherited = false; + controlsOnlySelection = true; + if (selCount > 0) + { + ICollection selection = SelectionService.GetSelectedComponents(); + foreach (object obj in selection) + { + if (!(obj is Control)) + { + controlsOnlySelection = false; + } + + if (!TypeDescriptor.GetAttributes(obj)[typeof(InheritanceAttribute)].Equals(InheritanceAttribute.NotInherited)) + { + _selectionInherited = true; + break; + } + } + } + OnUpdateCommandStatus(); + } + + /// + /// When this timer expires, this tells us that we need to erase any snaplines we have drawn. First, we need to marshal this back to the correct thread. + /// + private void OnSnapLineTimerExpire(object sender, EventArgs e) + { + Control marshalControl = BehaviorService.AdornerWindowControl; + if (marshalControl != null && marshalControl.IsHandleCreated) + { + marshalControl.BeginInvoke(new EventHandler(OnSnapLineTimerExpireMarshalled), new object[] { sender, e }); + } + } + + /// + /// Called when our snapline timer expires - this method has been call + /// has been properly marshalled back to the correct thread. + /// + private void OnSnapLineTimerExpireMarshalled(object sender, EventArgs e) + { + _snapLineTimer.Stop(); + EndDragManager(); + } + + /// + /// Determines the status of a menu command. Commands with this event handler are always enabled. + /// + protected void OnStatusAlways(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + cmd.Enabled = true; + } + + /// + /// Determines the status of a menu command. Commands with this event handler are enabled when one or more objects are selected. + /// + protected void OnStatusAnySelection(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + cmd.Enabled = selCount > 0; + } + + /// + /// Status for the copy command. This is enabled when there is something juicy selected. + /// + protected void OnStatusCopy(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + bool enable = false; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (!_selectionInherited && host != null && !host.Loading) + { + ISelectionService selSvc = (ISelectionService)GetService(typeof(ISelectionService)); + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || selSvc != null, "ISelectionService not found"); + if (selSvc != null) + { + // There must also be a component in the mix, and not the base component + ICollection selectedComponents = selSvc.GetSelectedComponents(); + object baseComp = host.RootComponent; + if (!selSvc.GetComponentSelected(baseComp)) + { + foreach (object obj in selectedComponents) + { + // if the object is not sited to the same thing as the host container then don't allow copy. + if (obj is IComponent comp && comp.Site != null && comp.Site.Container == host.Container) + { + enable = true; + break; + } + } + } + } + } + cmd.Enabled = enable; + } + + /// + /// Status for the cut command. This is enabled when there is something juicy selected and that something does not contain any inherited components. + /// + protected void OnStatusCut(object sender, EventArgs e) + { + OnStatusDelete(sender, e); + if (((MenuCommand)sender).Enabled) + { + OnStatusCopy(sender, e); + } + } + + /// + /// Status for the delete command. This is enabled when there is something selected and that something does not contain inherited components. + /// + protected void OnStatusDelete(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + if (_selectionInherited) + { + cmd.Enabled = false; + } + else + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + ISelectionService selSvc = (ISelectionService)GetService(typeof(ISelectionService)); + if (selSvc != null) + { + ICollection selectedComponents = selSvc.GetSelectedComponents(); + foreach (object obj in selectedComponents) + { + // if the object is not sited to the same thing as the host container then don't allow delete. + if (obj is IComponent comp && (comp.Site == null || (comp.Site != null && comp.Site.Container != host.Container))) + { + cmd.Enabled = false; + return; + } + } + } + } + OnStatusAnySelection(sender, e); + } + } + + /// + /// Determines the status of a menu command. Commands with this event are enabled when there is something yummy on the clipboard. + /// + protected void OnStatusPaste(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + // Before we even look at the data format, check to see if the thing we're going to paste into is privately inherited. If it is, then we definitely cannot paste. + if (primarySelection != null) + { + Debug.Assert(!CompModSwitches.CommonDesignerServices.Enabled || host != null, "IDesignerHost not found"); + if (host != null && host.GetDesigner(primarySelection) is ParentControlDesigner) + { + // This component is a target for our paste operation. We must ensure that it is not privately inherited. + InheritanceAttribute attr = (InheritanceAttribute)TypeDescriptor.GetAttributes(primarySelection)[typeof(InheritanceAttribute)]; + Debug.Assert(attr != null, "Type descriptor gave us a null attribute -- problem in type descriptor"); + if (attr.InheritanceLevel == InheritanceLevel.InheritedReadOnly) + { + cmd.Enabled = false; + return; + } + } + } + + // Not being inherited. Now look at the contents of the data + IDataObject dataObj = Clipboard.GetDataObject(); + bool enable = false; + if (dataObj != null) + { + if (dataObj.GetDataPresent(CF_DESIGNER)) + { + enable = true; + } + else + { + // Not ours, check to see if the toolbox service understands this + IToolboxService ts = (IToolboxService)GetService(typeof(IToolboxService)); + if (ts != null) + { + enable = (host != null ? ts.IsSupported(dataObj, host) : ts.IsToolboxItem(dataObj)); + } + } + } + cmd.Enabled = enable; + } + + private void OnStatusPrimarySelection(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + cmd.Enabled = primarySelection != null; + } + + protected virtual void OnStatusSelectAll(object sender, EventArgs e) + { + MenuCommand cmd = (MenuCommand)sender; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + cmd.Enabled = host.Container.Components.Count > 1; + } + + /// + /// This is called when the selection has changed. Anyone using CommandSetItems that need to update their status based on selection changes should override this and update their own commands at this time. The base implementaion runs through all base commands and calls UpdateStatus on them. + /// + protected virtual void OnUpdateCommandStatus() + { + // Now whip through all of the commands and ask them to update. + for (int i = 0; i < _commandSet.Length; i++) + { + _commandSet[i].UpdateStatus(); + } + } + + /// + /// This method grows the objects collection by one. It prepends the collection with a string[] which contains the component names in order for each component in the list. + /// + private ICollection PrependComponentNames(ICollection objects) + { + object[] newObjects = new object[objects.Count + 1]; + int idx = 1; + ArrayList names = new ArrayList(objects.Count); + foreach (object o in objects) + { + if (o is IComponent comp) + { + string name = null; + if (comp.Site != null) + { + name = comp.Site.Name; + } + names.Add(name); + } + newObjects[idx++] = o; + } + + string[] nameArray = new string[names.Count]; + names.CopyTo(nameArray, 0); + newObjects[0] = nameArray; + return newObjects; + } + + /// + /// called by the formatting commands when we need a given selection array sorted. Sorting the array sorts by x from left to right, and by Y from top to bottom. + /// + private void SortSelection(object[] selectedObjects, int nSortBy) + { + IComparer comp = null; + switch (nSortBy) + { + case SORT_HORIZONTAL: + comp = new ComponentLeftCompare(); + break; + case SORT_VERTICAL: + comp = new ComponentTopCompare(); + break; + case SORT_ZORDER: + comp = new ControlZOrderCompare(); + break; + default: + return; + } + Array.Sort(selectedObjects, comp); + } + + /// + /// Common function that updates the status of clipboard menu items only + /// + private void UpdateClipboardItems(object s, EventArgs e) + { + int itemCount = 0; + CommandSetItem curItem; + for (int i = 0; itemCount < 3 && i < _commandSet.Length; i++) + { + curItem = _commandSet[i]; + if (curItem.CommandID == MenuCommands.Paste || + curItem.CommandID == MenuCommands.Copy || + curItem.CommandID == MenuCommands.Cut) + { + itemCount++; + curItem.UpdateStatus(); + } + } + } + + private void UpdatePastePositions(ArrayList controls) + { + if (controls.Count == 0) + { + return; + } + + // Find the offset to apply to these controls. The offset is the location needed to center the controls in the parent. If there is no parent, we relocate to 0, 0. + Control parentControl = ((Control)controls[0]).Parent; + Point min = ((Control)controls[0]).Location; + Point max = min; + foreach (Control c in controls) + { + Point loc = c.Location; + Size size = c.Size; + if (min.X > loc.X) + { + min.X = loc.X; + } + if (min.Y > loc.Y) + { + min.Y = loc.Y; + } + if (max.X < loc.X + size.Width) + { + max.X = loc.X + size.Width; + } + if (max.Y < loc.Y + size.Height) + { + max.Y = loc.Y + size.Height; + } + } + + // We have the bounding rect for the controls. Next, offset this rect so that we center it in the parent. If we have no parent, the offset will position the control at 0, 0, to whatever parent we eventually get. + Point offset = new Point(-min.X, -min.Y); + // Look to ensure that we're not going to paste this control over the top of another control. We only do this for the first control because preserving the relationship between controls is more important than obscuring a control. + if (parentControl != null) + { + bool bumpIt; + bool wrapped = false; + Size parentSize = parentControl.ClientSize; + Size gridSize = Size.Empty; + Point parentOffset = new Point(parentSize.Width / 2, parentSize.Height / 2); + parentOffset.X -= (max.X - min.X) / 2; + parentOffset.Y -= (max.Y - min.Y) / 2; + + do + { + bumpIt = false; + // Cycle through the controls on the parent. We're interested in controls that (a) are not in our set of controls and (b) have a location == to our current bumpOffset OR (c) are the same size as our parent. If we find such a control, we increment the bump offset by one grid size. + foreach (Control child in parentControl.Controls) + { + Rectangle childBounds = child.Bounds; + if (controls.Contains(child)) + { + // We still want to bump if the child is the same size as the parent. Otherwise the child would overlay exactly on top of the parent. + if (!child.Size.Equals(parentSize)) + { + continue; + } + // We're dealing with our own pasted control, so offset its bounds. We don't use parent offset here because, well, we're comparing against the parent! + childBounds.Offset(offset); + } + + // We need only compare against one of our pasted controls, so pick the first one. + Control pasteControl = (Control)controls[0]; + Rectangle pasteControlBounds = pasteControl.Bounds; + pasteControlBounds.Offset(offset); + pasteControlBounds.Offset(parentOffset); + if (pasteControlBounds.Equals(childBounds)) + { + bumpIt = true; + if (gridSize.IsEmpty) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + IComponent baseComponent = host.RootComponent; + if (baseComponent != null && baseComponent is Control) + { + PropertyDescriptor gs = GetProperty(baseComponent, "GridSize"); + if (gs != null) + { + gridSize = (Size)gs.GetValue(baseComponent); + } + } + if (gridSize.IsEmpty) + { + gridSize.Width = 8; + gridSize.Height = 8; + } + } + + parentOffset += gridSize; + // Extra check: If the end of our control group is > the parent size, bump back to zero. We still allow further bumps after this so we can continue to offset, but if we cycle again then we quit so we won't loop indefinitely. We only do this if we're a group. If we're a single control we use the beginning of the control + a grid size. + int groupEndX; + int groupEndY; + if (controls.Count > 1) + { + groupEndX = parentOffset.X + max.X - min.X; + groupEndY = parentOffset.Y + max.Y - min.Y; + } + else + { + groupEndX = parentOffset.X + gridSize.Width; + groupEndY = parentOffset.Y + gridSize.Height; + } + + if (groupEndX > parentSize.Width || groupEndY > parentSize.Height) + { + parentOffset.X = 0; + parentOffset.Y = 0; + + if (wrapped) + { + bumpIt = false; + } + else + { + wrapped = true; + } + } + break; + } + } + } while (bumpIt); + offset.Offset(parentOffset.X, parentOffset.Y); + } + + // Now, for each control, update the offset. + if (parentControl != null) + { + parentControl.SuspendLayout(); + } + try + { + foreach (Control c in controls) + { + Point newLoc = c.Location; + newLoc.Offset(offset.X, offset.Y); + c.Location = newLoc; + } + } + finally + { + if (parentControl != null) + { + parentControl.ResumeLayout(); + } + } + + } + + private void UpdatePasteTabIndex(Control componentControl, object parentComponent) + { + if (!(parentComponent is Control parentControl) || componentControl == null) + { + return; + } + + bool tabIndexCollision = false; + int tabIndexOriginal = componentControl.TabIndex; + // Find the next highest tab index + int nextTabIndex = 0; + foreach (Control c in parentControl.Controls) + { + int t = c.TabIndex; + if (nextTabIndex <= t) + { + nextTabIndex = t + 1; + } + + if (t == tabIndexOriginal) + { + tabIndexCollision = true; + } + } + + if (tabIndexCollision) + { + componentControl.TabIndex = nextTabIndex; + } + } + + /// + /// We extend MenuCommand for our command set items. A command set item is a menu command with an added delegate that is used to determine the flags for the menu item. We have different classes of delegates here. For example, many menu items may be enabled when there is at least one object selected, while others are only enabled if there is more than one object or if there is a primary selection. + /// + protected class CommandSetItem : MenuCommand + { + private EventHandler _statusHandler; + private readonly IEventHandlerService _eventService; + private IUIService _uiService; + private readonly CommandSet _commandSet; + private static Hashtable s_commandStatusHash; // list of the command statuses we are tracking. + private bool _updatingCommand = false; // flag we set when we're updating the command so we don't call back on the status handler. + + public CommandSetItem(CommandSet commandSet, EventHandler statusHandler, EventHandler invokeHandler, CommandID id, IUIService uiService) : this(commandSet, statusHandler, invokeHandler, id, false, uiService) + { + } + + public CommandSetItem(CommandSet commandSet, EventHandler statusHandler, EventHandler invokeHandler, CommandID id) : this(commandSet, statusHandler, invokeHandler, id, false, null) + { + } + + public CommandSetItem(CommandSet commandSet, EventHandler statusHandler, EventHandler invokeHandler, CommandID id, bool optimizeStatus) : this(commandSet, statusHandler, invokeHandler, id, optimizeStatus, null) + { + } + + /// + /// Creates a new CommandSetItem. + /// + [SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity")] + public CommandSetItem(CommandSet commandSet, EventHandler statusHandler, EventHandler invokeHandler, CommandID id, bool optimizeStatus, IUIService uiService) + : base(invokeHandler, id) + { + _uiService = uiService; + _eventService = commandSet._eventService; + _statusHandler = statusHandler; + + // when we optimize, it's because status is fully based on selection. so what we do is only call the status handler once per selection change to prevent doing the same work over and over again. we do this by hashing up the command statuses and then filling in the results we get, so we can easily retrieve them when the selection hasn't changed. + if (optimizeStatus && statusHandler != null) + { + // we use this as our sentinel of when we're doing this. + _commandSet = commandSet; + // create the hash if needed. + lock (typeof(CommandSetItem)) + { + if (s_commandStatusHash == null) + { + s_commandStatusHash = new Hashtable(); + } + } + + // UNDONE:CommandSetItem is put in a static hashtable, and CommandSetItem references CommandSet, CommandSet reference FormDesigner. + // If we don't remove the CommandSetItem from the static hashtable, FormDesigner is leaked. This demonstrates a bad design. We should not keep a static hashtable for all the items, instead, we should keep a hashtable per Designer. When designer is disposed, all command items got disposed automatically. However, at this time, we would pick a simple way with low risks to fix this. if this handler isn't already in there, add it. + if (!(s_commandStatusHash[statusHandler] is StatusState state)) + { + state = new StatusState(); + s_commandStatusHash.Add(statusHandler, state); + } + state._refCount++; + } + } + + /// + /// Checks if the status for this command is valid, meaning we don't need to call the status handler. + /// + private bool CommandStatusValid + { + get + { + // check to see if this is a command we have hashed up and if it's version stamp is the same as our current selection version. + if (_commandSet != null && s_commandStatusHash.Contains(_statusHandler)) + { + if (s_commandStatusHash[_statusHandler] is StatusState state && state.SelectionVersion == _commandSet.SelectionVersion) + { + return true; + } + } + return false; + } + } + + /// + /// Applys the cached status to this item. + /// + private void ApplyCachedStatus() + { + if (_commandSet != null && s_commandStatusHash.Contains(_statusHandler)) + { + try + { + // set our our updating flag so it doesn't call the status handler again. + _updatingCommand = true; + + // and push the state into this command. + StatusState state = s_commandStatusHash[_statusHandler] as StatusState; + state.ApplyState(this); + } + finally + { + _updatingCommand = false; + } + } + } + + /// + /// This may be called to invoke the menu item. + /// + public override void Invoke() + { + // We allow outside parties to override the availability of particular menu commands. + try + { + if (_eventService != null) + { + IMenuStatusHandler msh = (IMenuStatusHandler)_eventService.GetHandler(typeof(IMenuStatusHandler)); + if (msh != null && msh.OverrideInvoke(this)) + { + return; + } + } + + base.Invoke(); + } + catch (Exception e) + { + if (_uiService != null) + { + _uiService.ShowError(e, string.Format(SR.CommandSetError, e.Message)); + } + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + } + + } + + /// + /// Only pass this down to the base when we're not doing the cached update. + /// + protected override void OnCommandChanged(EventArgs e) + { + if (!_updatingCommand) + { + base.OnCommandChanged(e); + } + } + + /// + /// Saves the status for this command to the statusstate that's stored in the hashtable based on our status handler delegate. + /// + private void SaveCommandStatus() + { + if (_commandSet != null) + { + StatusState state; + // see if we need to create one of these StatusState dudes. + if (s_commandStatusHash.Contains(_statusHandler)) + { + state = s_commandStatusHash[_statusHandler] as StatusState; + } + else + { + state = new StatusState(); + } + + // and save the enabled, visible, checked, and supported state. + state.SaveState(this, _commandSet.SelectionVersion); + } + } + + /// + /// Called when the status of this command should be re-queried. + /// + public void UpdateStatus() + { + // We allow outside parties to override the availability of particular menu commands. + if (_eventService != null) + { + IMenuStatusHandler msh = (IMenuStatusHandler)_eventService.GetHandler(typeof(IMenuStatusHandler)); + if (msh != null && msh.OverrideStatus(this)) + { + return; + } + } + + if (_statusHandler != null) + { + // if we need to update our status, call the status handler. otherwise, get the cached status and push it into this command. + if (!CommandStatusValid) + { + try + { + _statusHandler(this, EventArgs.Empty); + SaveCommandStatus(); + } + catch + { + } + } + else + { + ApplyCachedStatus(); + } + } + } + + /// + /// Remove this command item from the static hashtable to avoid leaking this object. + /// + public virtual void Dispose() + { + if (s_commandStatusHash[_statusHandler] is StatusState state) + { + state._refCount--; + if (state._refCount == 0) + { + s_commandStatusHash.Remove(_statusHandler); + } + } + } + + /// + /// This class saves the state for a given command. It keeps track of the results of the last status handler invocation and what "selection version" that happened on. + /// + private class StatusState + { + private const int Enabled = 0x01; + private const int Visible = 0x02; + private const int Checked = 0x04; + private const int Supported = 0x08; + private const int NeedsUpdate = 0x10; + private int _selectionVersion = 0; // the version of the selection that this was initialized with. + private int _statusFlags = NeedsUpdate; // our flags. + + // Multiple CommandSetItem instances can share a same status handler within a designer host. + // We use a simple ref count to make sure the CommandSetItem can be properly removed. + internal int _refCount = 0; + + public int SelectionVersion + { + get => _selectionVersion; + } + + /// + /// Pushes the state stored in this object into the given command item. + /// + internal void ApplyState(CommandSetItem item) + { + Debug.Assert((_statusFlags & NeedsUpdate) != NeedsUpdate, "Updating item when StatusState is not valid."); + item.Enabled = ((_statusFlags & Enabled) == Enabled); + item.Visible = ((_statusFlags & Visible) == Visible); + item.Checked = ((_statusFlags & Checked) == Checked); + item.Supported = ((_statusFlags & Supported) == Supported); + } + + /// + /// Updates this status object with the state from the given item, and saves teh seletion version. + /// + internal void SaveState(CommandSetItem item, int version) + { + _selectionVersion = version; + _statusFlags = 0; + if (item.Enabled) + { + _statusFlags |= Enabled; + } + if (item.Visible) + { + _statusFlags |= Visible; + } + if (item.Checked) + { + _statusFlags |= Checked; + } + if (item.Supported) + { + _statusFlags |= Supported; + } + } + } + } + + /// + /// The immediate command set item is used for commands that cannot be cached. + /// Commands such as Paste that get outside stimulus cannot be cached by our menu system, so they get an ImmediateCommandSetItem instead of a CommandSetItem. + /// + protected class ImmediateCommandSetItem : CommandSetItem + { + /// + /// Creates a new ImmediateCommandSetItem. + /// + public ImmediateCommandSetItem(CommandSet commandSet, EventHandler statusHandler, EventHandler invokeHandler, CommandID id, IUIService uiService) + : base(commandSet, statusHandler, invokeHandler, id, uiService) + { + } + + /// + /// Overrides OleStatus in MenuCommand to invoke our status handler first. + /// + public override int OleStatus + { + get + { + UpdateStatus(); + return base.OleStatus; + } + } + } + + /// + /// Component comparer that compares the left property of a component. + /// + private class ComponentLeftCompare : IComparer + { + public int Compare(object p, object q) + { + PropertyDescriptor pProp = TypeDescriptor.GetProperties(p)["Location"]; + PropertyDescriptor qProp = TypeDescriptor.GetProperties(q)["Location"]; + Point pLoc = (Point)pProp.GetValue(p); + Point qLoc = (Point)qProp.GetValue(q); + + //if our lefts are equal, then compare tops + if (pLoc.X == qLoc.X) + { + return pLoc.Y - qLoc.Y; + } + + return pLoc.X - qLoc.X; + } + } + + /// + /// Component comparer that compares the top property of a component. + /// + private class ComponentTopCompare : IComparer + { + public int Compare(object p, object q) + { + PropertyDescriptor pProp = TypeDescriptor.GetProperties(p)["Location"]; + PropertyDescriptor qProp = TypeDescriptor.GetProperties(q)["Location"]; + Point pLoc = (Point)pProp.GetValue(p); + Point qLoc = (Point)qProp.GetValue(q); + + //if our tops are equal, then compare lefts + if (pLoc.Y == qLoc.Y) + { + return pLoc.X - qLoc.X; + } + + return pLoc.Y - qLoc.Y; + } + } + + private class ControlZOrderCompare : IComparer + { + + public int Compare(object p, object q) + { + if (p == null) + { + return -1; + } + else if (q == null) + { + return 1; + } + else if (p == q) + { + return 0; + } + + if (!(p is Control c1) || !(q is Control c2)) + { + return 1; + } + + if (c1.Parent == c2.Parent && c1.Parent != null) + { + return c1.Parent.Controls.GetChildIndex(c1) - c1.Parent.Controls.GetChildIndex(c2); + } + return 1; + } + } + + private class TabIndexCompare : IComparer + { + public int Compare(object p, object q) + { + Control c1 = p as Control; + Control c2 = q as Control; + if (c1 == c2) + { + return 0; + } + + if (c1 == null) + { + return -1; + } + + if (c2 == null) + { + return 1; + } + return c1.TabIndex - c2.TabIndex; + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentActionsType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentActionsType.cs new file mode 100644 index 00000000000..f97c068f51f --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentActionsType.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + public enum ComponentActionsType + { + All, Component, Service + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentTray.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentTray.cs index 891824abe8d..ce1f7336860 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentTray.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ComponentTray.cs @@ -2,18 +2,24 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; +using System.ComponentModel.Design.Serialization; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Design; +using System.Drawing.Drawing2D; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms.Design.Behavior; +using Microsoft.Win32; namespace System.Windows.Forms.Design { /// - /// - /// Provides the component tray UI for the form designer. - /// + /// Provides the component tray UI for the form designer. /// [ToolboxItem(false)] [DesignTimeVisible(false)] @@ -21,202 +27,1148 @@ namespace System.Windows.Forms.Design [ProvideProperty("TrayLocation", typeof(IComponent))] public class ComponentTray : ScrollableControl, IExtenderProvider, ISelectionUIHandler, IOleDragClient { - // Empty class for build time dependancy + + private static readonly Point InvalidPoint = new Point(int.MinValue, int.MinValue); + + private IServiceProvider serviceProvider; // Where services come from. + private Point whiteSpace = Point.Empty; // space to leave between components. + private Size grabHandle = Size.Empty; // Size of the grab handles. + private ArrayList controls; // List of items in the tray in the order of their layout. + private SelectionUIHandler dragHandler; // the thing responsible for handling mouse drags + private ISelectionUIService selectionUISvc; // selectiuon UI; we use this a lot + private IToolboxService toolboxService; // cached for drag/drop /// - /// Creates a new component tray. The component tray - /// will monitor component additions and removals and create - /// appropriate UI objects in its space. + /// Provides drag and drop functionality through OLE. + /// + internal OleDragDropHandler oleDragDropHandler; // handler class for ole drag drop operations. + + private IDesigner mainDesigner; // the designer that is associated with this tray + private IEventHandlerService eventHandlerService = null; // Event Handler service to handle keyboard and focus. + private bool queriedTabOrder; + private MenuCommand tabOrderCommand; + private ICollection selectedObjects; + + // Services that we use on a high enough frequency to merit caching. + private IMenuCommandService menuCommandService; + private CommandSet privateCommandSet = null; + private InheritanceUI inheritanceUI; + private Point mouseDragStart = InvalidPoint; // the starting location of a drag + private Point mouseDragEnd = InvalidPoint; // the ending location of a drag + private Rectangle mouseDragWorkspace = Rectangle.Empty; // a temp work rectangle we cache for perf + private ToolboxItem mouseDragTool; // the tool that's being dragged; only for drag/drop + private Point mouseDropLocation = InvalidPoint; // where the tool was dropped + private bool showLargeIcons = false; // Show Large icons or not. + private bool autoArrange = false; // allows for auto arranging icons. + private Point autoScrollPosBeforeDragging = Point.Empty; //Used to return the correct scroll pos. after a drag + + // Component Tray Context menu items... + private MenuCommand _menucmdArrangeIcons = null; + private MenuCommand _menucmdLineupIcons = null; + private MenuCommand _menucmdLargeIcons = null; + private bool _fResetAmbient = false; + private bool _fSelectionChanged = false; + private ComponentTrayGlyphManager glyphManager; //used to manage any glyphs added to the tray + + /// + /// Creates a new component tray. The component tray will monitor component additions and removals and create appropriate UI objects in its space. /// [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] public ComponentTray(IDesigner mainDesigner, IServiceProvider serviceProvider) { - throw new NotImplementedException(SR.NotImplementedByDesign); + AutoScroll = true; + this.mainDesigner = mainDesigner; + this.serviceProvider = serviceProvider; + AllowDrop = true; + Text = "ComponentTray"; // makes debugging easier + SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true); + + controls = new ArrayList(); + + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService)); + Debug.Assert(es != null, "Component tray wants an extender provider service, but there isn't one."); + if (es != null) + { + es.AddExtenderProvider(this); + } + + if (GetService(typeof(IEventHandlerService)) == null) + { + if (host != null) + { + eventHandlerService = new EventHandlerService(this); + host.AddService(typeof(IEventHandlerService), eventHandlerService); + } + } + + IMenuCommandService mcs = MenuService; + if (mcs != null) + { + Debug.Assert(_menucmdArrangeIcons == null, "Non-Null Menu Command for ArrangeIcons"); + Debug.Assert(_menucmdLineupIcons == null, "Non-Null Menu Command for LineupIcons"); + Debug.Assert(_menucmdLargeIcons == null, "Non-Null Menu Command for LargeIcons"); + + _menucmdArrangeIcons = new MenuCommand(new EventHandler(OnMenuArrangeIcons), StandardCommands.ArrangeIcons); + _menucmdLineupIcons = new MenuCommand(new EventHandler(OnMenuLineupIcons), StandardCommands.LineupIcons); + _menucmdLargeIcons = new MenuCommand(new EventHandler(OnMenuShowLargeIcons), StandardCommands.ShowLargeIcons); + + _menucmdArrangeIcons.Checked = AutoArrange; + _menucmdLargeIcons.Checked = ShowLargeIcons; + mcs.AddCommand(_menucmdArrangeIcons); + mcs.AddCommand(_menucmdLineupIcons); + mcs.AddCommand(_menucmdLargeIcons); + } + + IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + + if (componentChangeService != null) + { + componentChangeService.ComponentRemoved += new ComponentEventHandler(OnComponentRemoved); + } + + IUIService uiService = GetService(typeof(IUIService)) as IUIService; + if (uiService != null) + { + Color styleColor; + + if (uiService.Styles["ArtboardBackground"] is Color) + { + styleColor = (Color)uiService.Styles["ArtboardBackground"]; + } + //Can't use 'as' here since Color is a value type + else if (uiService.Styles["VsColorDesignerTray"] is Color) + { + styleColor = (Color)uiService.Styles["VsColorDesignerTray"]; + } + else if (uiService.Styles["HighlightColor"] is Color) + { + // Since v1, we have had code here that checks for HighlightColor, so some hosts (like WinRes) have been setting it. If VsColorDesignerTray isn't present, we look for HighlightColor for backward compat. + styleColor = (Color)uiService.Styles["HighlightColor"]; + } + else + { + //No style color provided? Let's pick a default. + styleColor = SystemColors.Info; + } + + if (uiService.Styles["ArtboardBackgroundText"] is Color) + { + ForeColor = (Color)uiService.Styles["ArtboardBackgroundText"]; + } + else if (uiService.Styles["VsColorPanelText"] is Color) + { + ForeColor = (Color)uiService.Styles["VsColorPanelText"]; + } + + BackColor = styleColor; + Font = (Font)uiService.Styles["DialogFont"]; + } + + ISelectionService selSvc = (ISelectionService)GetService(typeof(ISelectionService)); + if (selSvc != null) + { + selSvc.SelectionChanged += new EventHandler(OnSelectionChanged); + } + + // Listen to the SystemEvents so that we can resync selection based on display settings etc. + SystemEvents.DisplaySettingsChanged += new EventHandler(OnSystemSettingChanged); + SystemEvents.InstalledFontsChanged += new EventHandler(OnSystemSettingChanged); + SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + + // Listen to refresh events from TypeDescriptor. If a component gets refreshed, we re-query + // and will hide/show the view based on the DesignerView attribute. + // + TypeDescriptor.Refreshed += new RefreshEventHandler(OnComponentRefresh); + + BehaviorService behSvc = GetService(typeof(BehaviorService)) as BehaviorService; + if (behSvc != null) + { + //this object will manage any glyphs that get added to our tray + glyphManager = new ComponentTrayGlyphManager(selSvc, behSvc); + } + } + private InheritanceUI InheritanceUI + { + get + { + if (inheritanceUI == null) + { + inheritanceUI = new InheritanceUI(); + } + return inheritanceUI; + } + } + + /// + /// Retrieves the menu editor service, which we cache for speed. + /// + private IMenuCommandService MenuService + { + get + { + if (menuCommandService == null) + { + menuCommandService = (IMenuCommandService)GetService(typeof(IMenuCommandService)); + } + return menuCommandService; + } + } + + private class ComponentTrayGlyphManager + { + private Adorner _traySelectionAdorner; //we'll use a single adorner to manage the glyphs + private Glyph _hitTestedGlyph; //the last glyph we hit tested (can be null) + + private readonly ISelectionService _selSvc; //we need the selection service fo r the hover behavior + private readonly BehaviorService _behaviorSvc; + + public ComponentTrayGlyphManager(ISelectionService selSvc, BehaviorService behaviorSvc) + { + _selSvc = selSvc; + _behaviorSvc = behaviorSvc; + _traySelectionAdorner = new Adorner(); + } + + public GlyphCollection SelectionGlyphs + { + get => _traySelectionAdorner.Glyphs; + } + + public void Dispose() + { + if (_traySelectionAdorner != null) + { + _traySelectionAdorner.Glyphs.Clear(); + _traySelectionAdorner = null; + } + } + + public GlyphCollection GetGlyphsForComponent(IComponent comp) + { + GlyphCollection glyphs = new GlyphCollection(); + if (_behaviorSvc != null && comp != null) + { + if (_behaviorSvc.DesignerActionUI != null) + { + Glyph g = _behaviorSvc.DesignerActionUI.GetDesignerActionGlyph(comp); + if (g != null) + { + glyphs.Add(g); + } + } + } + return glyphs; + } + + public Cursor GetHitTest(Point p) + { + for (int i = 0; i < _traySelectionAdorner.Glyphs.Count; i++) + { + Cursor hitTestCursor = _traySelectionAdorner.Glyphs[i].GetHitTest(p); + if (hitTestCursor != null) + { + _hitTestedGlyph = _traySelectionAdorner.Glyphs[i]; + return hitTestCursor; + } + } + + _hitTestedGlyph = null; + return null; + } + + public bool OnMouseDoubleClick(MouseEventArgs e) + { + if (_hitTestedGlyph != null && _hitTestedGlyph.Behavior != null) + { + return _hitTestedGlyph.Behavior.OnMouseDoubleClick(_hitTestedGlyph, e.Button, new Point(e.X, e.Y)); + } + return false; + } + + public bool OnMouseDown(MouseEventArgs e) + { + if (_hitTestedGlyph != null && _hitTestedGlyph.Behavior != null) + { + return _hitTestedGlyph.Behavior.OnMouseDown(_hitTestedGlyph, e.Button, new Point(e.X, e.Y)); + } + return false; + } + + public bool OnMouseMove(MouseEventArgs e) + { + if (_hitTestedGlyph != null && _hitTestedGlyph.Behavior != null) + { + return _hitTestedGlyph.Behavior.OnMouseMove(_hitTestedGlyph, e.Button, new Point(e.X, e.Y)); + } + return false; + } + + public bool OnMouseUp(MouseEventArgs e) + { + if (_hitTestedGlyph != null && _hitTestedGlyph.Behavior != null) + { + return _hitTestedGlyph.Behavior.OnMouseUp(_hitTestedGlyph, e.Button); + } + return false; + } + + public void OnPaintGlyphs(PaintEventArgs pe) + { + //Paint any glyphs our tray adorner has + foreach (Glyph g in _traySelectionAdorner.Glyphs) + { + g.Paint(pe); + } + } + + public void UpdateLocation(TrayControl trayControl) + { + + foreach (Glyph g in _traySelectionAdorner.Glyphs) + { + // only look at glyphs that derive from designerglyph base (actions) + if (g is DesignerActionGlyph desGlyph && ((DesignerActionBehavior)(desGlyph.Behavior)).RelatedComponent.Equals(trayControl.Component)) + { + desGlyph.UpdateAlternativeBounds(trayControl.Bounds); + } + } + } + } + + private void OnMenuArrangeIcons(object sender, EventArgs e) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + DesignerTransaction t = null; + + try + { + t = host.CreateTransaction(SR.TrayAutoArrange); + + PropertyDescriptor trayAAProp = TypeDescriptor.GetProperties(mainDesigner.Component)["TrayAutoArrange"]; + if (trayAAProp != null) + { + trayAAProp.SetValue(mainDesigner.Component, !AutoArrange); + } + } + finally + { + if (t != null) + t.Commit(); + } + } + private void OnMenuLineupIcons(object sender, EventArgs e) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + DesignerTransaction t = null; + try + { + t = host.CreateTransaction(SR.TrayLineUpIcons); + DoLineupIcons(); + } + finally + { + if (t != null) + t.Commit(); + } + } + private void DoLineupIcons() + { + if (autoArrange) + return; + + bool oldValue = autoArrange; + autoArrange = true; + + try + { + DoAutoArrange(true); + } + finally + { + autoArrange = oldValue; + } + } + + private void OnMenuShowLargeIcons(object sender, EventArgs e) + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + DesignerTransaction t = null; + + try + { + t = host.CreateTransaction(SR.TrayShowLargeIcons); + PropertyDescriptor trayIconProp = TypeDescriptor.GetProperties(mainDesigner.Component)["TrayLargeIcon"]; + if (trayIconProp != null) + { + trayIconProp.SetValue(mainDesigner.Component, !ShowLargeIcons); + } + } + finally + { + if (t != null) + t.Commit(); + } + } + + private void OnComponentRemoved(object sender, ComponentEventArgs cevent) + { + RemoveComponent(cevent.Component); + } + + private void OnSelectionChanged(object sender, EventArgs e) + { + selectedObjects = ((ISelectionService)sender).GetSelectedComponents(); + object primary = ((ISelectionService)sender).PrimarySelection; + Invalidate(); + _fSelectionChanged = true; + + // Accessibility information + foreach (object selObj in selectedObjects) + { + if (selObj is IComponent component) + { + Control c = TrayControl.FromComponent(component); + if (c != null) + { + Debug.WriteLineIf(CompModSwitches.MSAA.TraceInfo, "MSAA: SelectionAdd, traycontrol = " + c.ToString()); + UnsafeNativeMethods.NotifyWinEvent((int)AccessibleEvents.SelectionAdd, new HandleRef(c, c.Handle), NativeMethods.OBJID_CLIENT, 0); + } + } + } + + if (primary is IComponent comp) + { + Control c = TrayControl.FromComponent(comp); + if (c != null && IsHandleCreated) + { + ScrollControlIntoView(c); + UnsafeNativeMethods.NotifyWinEvent((int)AccessibleEvents.Focus, new HandleRef(c, c.Handle), NativeMethods.OBJID_CLIENT, 0); + } + if (glyphManager != null) + { + glyphManager.SelectionGlyphs.Clear(); + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + foreach (object selObj in selectedObjects) + { + if (selObj is IComponent selectedComponent && !(host.GetDesigner(selectedComponent) is ControlDesigner)) + { // don't want to do it for controls that are also in the tray + GlyphCollection glyphs = glyphManager.GetGlyphsForComponent(selectedComponent); + if (glyphs != null && glyphs.Count > 0) + { + SelectionGlyphs.AddRange(glyphs); + } + } + } + } + } + } + private void OnComponentRefresh(RefreshEventArgs e) + { + IComponent component = e.ComponentChanged as IComponent; + + if (component != null) + { + TrayControl control = TrayControl.FromComponent(component); + + if (control != null) + { + bool shouldDisplay = CanDisplayComponent(component); + if (shouldDisplay != control.Visible || !shouldDisplay) + { + control.Visible = shouldDisplay; + Rectangle bounds = control.Bounds; + bounds.Inflate(grabHandle); + bounds.Inflate(grabHandle); + Invalidate(bounds); + PerformLayout(); + } + } + } + } + + /// + /// Internally exposes a way for sited components to add glyphs to the component tray. + /// + internal GlyphCollection SelectionGlyphs + { + get + { + if (glyphManager != null) + { + return glyphManager.SelectionGlyphs; + } + else + { + return null; + } + } + } + + private delegate void AsyncInvokeHandler(bool children); + + private void OnSystemSettingChanged(object sender, EventArgs e) + { + if (IsHandleCreated) + { + _fResetAmbient = true; + ResetTrayControls(); + BeginInvoke(new AsyncInvokeHandler(Invalidate), new object[] { true }); + } + } + + private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + if (IsHandleCreated) + { + _fResetAmbient = true; + ResetTrayControls(); + BeginInvoke(new AsyncInvokeHandler(Invalidate), new object[] { true }); + } + } + private void ResetTrayControls() + { + ControlCollection children = (ControlCollection)Controls; + if (children == null) + return; + + for (int i = 0; i < children.Count; ++i) + { + if (children[i] is TrayControl tc) + { + tc._fRecompute = true; + } + } } public bool AutoArrange { - get => throw new NotImplementedException(SR.NotImplementedByDesign); + get + { + return autoArrange; + } + + set + { + if (autoArrange != value) + { + autoArrange = value; + _menucmdArrangeIcons.Checked = value; - set => throw new NotImplementedException(SR.NotImplementedByDesign); + if (autoArrange) + { + DoAutoArrange(true); + } + } + } + } + private void DoAutoArrange(bool dirtyDesigner) + { + if (controls == null || controls.Count <= 0) + { + return; + } + + controls.Sort(new AutoArrangeComparer()); + SuspendLayout(); + + //Reset the autoscroll position before auto arranging. + //This way, when OnLayout gets fired after this, we won't have to move every component again. Note that sync'ing the selection will automatically select & scroll into view the right components + AutoScrollPosition = new Point(0, 0); + try + { + Control prevCtl = null; + bool positionedGlobal = true; + foreach (Control ctl in controls) + { + if (!ctl.Visible) + continue; + // If we're auto arranging, always move the control. If not, move the control only if it was never given a position. + //his auto arranges it until the user messes with it, or until its position is saved into the resx. (if one control is no longer positioned, move all the other one as we don't want them to go under one another) + if (autoArrange) + { + PositionInNextAutoSlot(ctl as TrayControl, prevCtl, dirtyDesigner); + } + else if (!((TrayControl)ctl).Positioned || !positionedGlobal) + { + PositionInNextAutoSlot(ctl as TrayControl, prevCtl, false); + positionedGlobal = false; + } + prevCtl = ctl; + } + + if (selectionUISvc != null) + { + selectionUISvc.SyncSelection(); + } + } + finally + { + ResumeLayout(); + } + } + private bool PositionInNextAutoSlot(TrayControl c, Control prevCtl, bool dirtyDesigner) + { + Debug.Assert(c.Visible, "TrayControl for " + c.Component + " should not be positioned"); + + if (whiteSpace.IsEmpty) + { + Debug.Assert(selectionUISvc != null, "No SelectionUIService available for tray."); + whiteSpace = new Point(selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle)); + whiteSpace.X = whiteSpace.X * 2 + 3; + whiteSpace.Y = whiteSpace.Y * 2 + 3; + } + + if (prevCtl == null) + { + Rectangle display = DisplayRectangle; + Point newLoc = new Point(display.X + whiteSpace.X, display.Y + whiteSpace.Y); + if (!c.Location.Equals(newLoc)) + { + c.Location = newLoc; + if (dirtyDesigner) + { + IComponent comp = c.Component; + Debug.Assert(comp != null, "Component for the TrayControl is null"); + + PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"]; + if (ctlLocation != null) + { + Point autoScrollLoc = AutoScrollPosition; + newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y); + ctlLocation.SetValue(comp, newLoc); + } + } + else + { + c.Location = newLoc; + } + return true; + } + } + else + { + // Calcuate the next location for this control. + Rectangle bounds = prevCtl.Bounds; + Point newLoc = new Point(bounds.X + bounds.Width + whiteSpace.X, bounds.Y); + + // Check to see if it goes over the edge of our window. If it does, then wrap it. + if (newLoc.X + c.Size.Width > Size.Width) + { + newLoc.X = whiteSpace.X; + newLoc.Y += bounds.Height + whiteSpace.Y; + } + + if (!c.Location.Equals(newLoc)) + { + if (dirtyDesigner) + { + IComponent comp = c.Component; + Debug.Assert(comp != null, "Component for the TrayControl is null"); + + PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"]; + if (ctlLocation != null) + { + Point autoScrollLoc = this.AutoScrollPosition; + newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y); + ctlLocation.SetValue(comp, newLoc); + } + } + else + { + c.Location = newLoc; + } + return true; + } + } + return false; } /// - /// - /// Gets the number of compnents contained within this tray. - /// + /// Gets the number of compnents contained within this tray. /// - public int ComponentCount => throw new NotImplementedException(SR.NotImplementedByDesign); + public int ComponentCount + { + get => Controls.Count; + } /// - /// Determines whether the tray will show large icon view or not. + /// Determines whether the tray will show large icon view or not. /// public bool ShowLargeIcons { - get => throw new NotImplementedException(SR.NotImplementedByDesign); + get => showLargeIcons; + set + { + if (showLargeIcons != value) + { + showLargeIcons = value; + _menucmdLargeIcons.Checked = ShowLargeIcons; + ResetTrayControls(); + Invalidate(true); + } + } + } + + private bool TabOrderActive + { + get + { + if (!queriedTabOrder) + { + queriedTabOrder = true; + IMenuCommandService mcs = MenuService; + if (mcs != null) + { + tabOrderCommand = mcs.FindCommand(MenuCommands.TabOrder); + } + } - set => throw new NotImplementedException(SR.NotImplementedByDesign); + if (tabOrderCommand != null) + { + return tabOrderCommand.Checked; + } + return false; + } } bool IExtenderProvider.CanExtend(object extendee) { - throw new NotImplementedException(); + return (extendee is IComponent comp) && (TrayControl.FromComponent(comp) != null); } - IComponent IOleDragClient.Component => throw new NotImplementedException(); + IComponent IOleDragClient.Component + { + get => mainDesigner.Component; + } - bool IOleDragClient.CanModifyComponents => throw new NotImplementedException(); + bool IOleDragClient.CanModifyComponents + { + get => true; + } bool IOleDragClient.AddComponent(IComponent component, string name, bool firstAdd) { - throw new NotImplementedException(); + // the designer for controls decides what to do here + if (mainDesigner is IOleDragClient oleDragClient) + { + try + { + oleDragClient.AddComponent(component, name, firstAdd); + PositionControl(TrayControl.FromComponent(component)); + mouseDropLocation = InvalidPoint; + return true; + } + catch + { + } + } + else + { + // for webforms (98109) just add the component directly to the host + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + try + { + if (host != null && host.Container != null) + { + if (host.Container.Components[name] != null) + { + name = null; + } + host.Container.Add(component, name); + return true; + } + } + catch + { + } + + } + Debug.Fail("Don't know how to add component!"); + return false; } bool IOleDragClient.IsDropOk(IComponent component) { - throw new NotImplementedException(); + return true; } Control IOleDragClient.GetDesignerControl() { - throw new NotImplementedException(); + return this; } Control IOleDragClient.GetControlForComponent(object component) { - throw new NotImplementedException(); + IComponent comp = component as IComponent; + if (comp != null) + { + return TrayControl.FromComponent(comp); + } + Debug.Fail("component is not IComponent"); + return null; } bool ISelectionUIHandler.BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY) { - throw new NotImplementedException(); - } + if (TabOrderActive) + { + return false; + } - void ISelectionUIHandler.DragMoved(object[] components, Rectangle offset) - { - throw new NotImplementedException(); + bool result = DragHandler.BeginDrag(components, rules, initialX, initialY); + if (result) + { + if (!GetOleDragHandler().DoBeginDrag(components, rules, initialX, initialY)) + { + return false; + } + } + return result; } + void ISelectionUIHandler.DragMoved(object[] components, Rectangle offset) => DragHandler.DragMoved(components, offset); + void ISelectionUIHandler.EndDrag(object[] components, bool cancel) { - throw new NotImplementedException(); + DragHandler.EndDrag(components, cancel); + GetOleDragHandler().DoEndDrag(components, cancel); + // Here, after the drag is finished and after we have resumed layout, adjust the location of the components we dragged by the scroll offset + if (!autoScrollPosBeforeDragging.IsEmpty) + { + foreach (IComponent comp in components) + { + TrayControl tc = TrayControl.FromComponent(comp); + if (tc != null) + { + SetTrayLocation(comp, new Point(tc.Location.X - autoScrollPosBeforeDragging.X, tc.Location.Y - autoScrollPosBeforeDragging.Y)); + } + } + AutoScrollPosition = new Point(-autoScrollPosBeforeDragging.X, -autoScrollPosBeforeDragging.Y); + } } Rectangle ISelectionUIHandler.GetComponentBounds(object component) { - throw new NotImplementedException(); + // We render the selection UI glyph ourselves. + return Rectangle.Empty; } - SelectionRules ISelectionUIHandler.GetComponentRules(object component) - { - throw new NotImplementedException(); - } + SelectionRules ISelectionUIHandler.GetComponentRules(object component) => SelectionRules.Visible | SelectionRules.Moveable; Rectangle ISelectionUIHandler.GetSelectionClipRect(object component) { - throw new NotImplementedException(); + if (IsHandleCreated) + { + return RectangleToScreen(ClientRectangle); + } + return Rectangle.Empty; } void ISelectionUIHandler.OnSelectionDoubleClick(IComponent component) { - throw new NotImplementedException(); + if (!TabOrderActive) + { + if (((IOleDragClient)this).GetControlForComponent(component) is TrayControl tc) + { + tc.ViewDefaultEvent(component); + } + } } - bool ISelectionUIHandler.QueryBeginDrag(object[] components, SelectionRules rules, int initialX, int initialY) - { - throw new NotImplementedException(); - } + bool ISelectionUIHandler.QueryBeginDrag(object[] components, SelectionRules rules, int initialX, int initialY) => DragHandler.QueryBeginDrag(components, rules, initialX, initialY); void ISelectionUIHandler.ShowContextMenu(IComponent component) { - throw new NotImplementedException(); + Point cur = Control.MousePosition; + OnContextMenu(cur.X, cur.Y, true); } - void ISelectionUIHandler.OleDragEnter(DragEventArgs de) - { - throw new NotImplementedException(); - } + void ISelectionUIHandler.OleDragEnter(DragEventArgs de) => GetOleDragHandler().DoOleDragEnter(de); - void ISelectionUIHandler.OleDragDrop(DragEventArgs de) - { - throw new NotImplementedException(); - } + void ISelectionUIHandler.OleDragDrop(DragEventArgs de) => GetOleDragHandler().DoOleDragDrop(de); - void ISelectionUIHandler.OleDragOver(DragEventArgs de) - { - throw new NotImplementedException(); - } + void ISelectionUIHandler.OleDragOver(DragEventArgs de) => GetOleDragHandler().DoOleDragOver(de); - void ISelectionUIHandler.OleDragLeave() - { - throw new NotImplementedException(); - } + void ISelectionUIHandler.OleDragLeave() => GetOleDragHandler().DoOleDragLeave(); /// - /// Adds a component to the tray. + /// Adds a component to the tray. /// public virtual void AddComponent(IComponent component) { - throw new NotImplementedException(SR.NotImplementedByDesign); + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + // Ignore components that cannot be added to the tray + if (!CanDisplayComponent(component)) + { + return; + } + + // And designate us as the selection UI handler for the control. + if (selectionUISvc == null) + { + selectionUISvc = (ISelectionUIService)GetService(typeof(ISelectionUIService)); + // If there is no selection service, then we will provide our own. + if (selectionUISvc == null) + { + selectionUISvc = new SelectionUIService(host); + host.AddService(typeof(ISelectionUIService), selectionUISvc); + } + grabHandle = selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle); + } + + // Create a new instance of a tray control. + TrayControl trayctl = new TrayControl(this, component); + SuspendLayout(); + try + { + // Add it to us + Controls.Add(trayctl); + controls.Add(trayctl); + // CanExtend can actually be called BEFORE the component is added to the ComponentTray. + // ToolStrip is such as scenario: + // 1. Add a timer to the Tray. + // 2. Add a ToolStrip. + // 3. ToolStripDesigner.Initialize will be called before ComponentTray.AddComponent, so the ToolStrip is not yet added to the tray. + // 4. TooStripDesigner.Initialize calls GetProperties, which causes our CanExtend to be called. + // 5. CanExtend will return false, since the component has not yet been added. + // 6. This causes all sorts of badness/ + // Fix is to refresh. + TypeDescriptor.Refresh(component); + if (host != null && !host.Loading) + { + PositionControl(trayctl); + } + if (selectionUISvc != null) + { + selectionUISvc.AssignSelectionUIHandler(component, this); + } + + InheritanceAttribute attr = trayctl.InheritanceAttribute; + if (attr.InheritanceLevel != InheritanceLevel.NotInherited) + { + InheritanceUI iui = InheritanceUI; + if (iui != null) + { + iui.AddInheritedControl(trayctl, attr.InheritanceLevel); + } + } + } + finally + { + ResumeLayout(); + } + + if (host != null && !host.Loading) + { + ScrollControlIntoView(trayctl); + } } [CLSCompliant(false)] protected virtual bool CanCreateComponentFromTool(ToolboxItem tool) { - throw new NotImplementedException(SR.NotImplementedByDesign); + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + Debug.Assert(host != null, "Service object could not provide us with a designer host."); + // Disallow controls to be added to the component tray. + Type compType = host.GetType(tool.TypeName); + if (compType == null) + return true; + if (!compType.IsSubclassOf(typeof(Control))) + { + return true; + } + + Type designerType = GetDesignerType(compType, typeof(IDesigner)); + if (typeof(ControlDesigner).IsAssignableFrom(designerType)) + { + return false; + } + return true; + } + + private Type GetDesignerType(Type t, Type designerBaseType) + { + Type designerType = null; + // Get the set of attributes for this type + AttributeCollection attributes = TypeDescriptor.GetAttributes(t); + for (int i = 0; i < attributes.Count; i++) + { + if (attributes[i] is DesignerAttribute da) + { + Type attributeBaseType = Type.GetType(da.DesignerBaseTypeName); + if (attributeBaseType != null && attributeBaseType == designerBaseType) + { + bool foundService = false; + ITypeResolutionService tr = (ITypeResolutionService)GetService(typeof(ITypeResolutionService)); + if (tr != null) + { + foundService = true; + designerType = tr.GetType(da.DesignerTypeName); + } + + if (!foundService) + { + designerType = Type.GetType(da.DesignerTypeName); + } + + if (designerType != null) + { + break; + } + } + } + } + return designerType; } /// - /// This method determines if a UI representation for the given component should be provided. - /// If it returns true, then the component will get a glyph in the tray area. If it returns - /// false, then the component will not actually be added to the tray. The default - /// implementation looks for DesignTimeVisibleAttribute.Yes on the component's class. + /// This method determines if a UI representation for the given component should be provided. + /// If it returns true, then the component will get a glyph in the tray area. If it returns false, then the component will not actually be added to the tray. + /// The default implementation looks for DesignTimeVisibleAttribute.Yes on the component's class. /// protected virtual bool CanDisplayComponent(IComponent component) { - throw new NotImplementedException(SR.NotImplementedByDesign); + return TypeDescriptor.GetAttributes(component).Contains(DesignTimeVisibleAttribute.Yes); } [CLSCompliant(false)] public void CreateComponentFromTool(ToolboxItem tool) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (!CanCreateComponentFromTool(tool)) + { + return; + } + // We invoke the drag drop handler for this. This implementation is shared between all designers that create components. + GetOleDragHandler().CreateTool(tool, null, 0, 0, 0, 0, false, false); } /// - /// Displays the given exception to the user. + /// Displays the given exception to the user. /// protected void DisplayError(Exception e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + IUIService uis = (IUIService)GetService(typeof(IUIService)); + if (uis != null) + { + uis.ShowError(e); + } + else + { + string message = e.Message; + if (message == null || message.Length == 0) + { + message = e.ToString(); + } + RTLAwareMessageBox.Show(null, message, null, MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, 0); + } } - // - /// - /// - /// Disposes of the resources (other than memory) used by the component tray object. - /// + /// Disposes of the resources (other than memory) used by the component tray object. /// protected override void Dispose(bool disposing) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (disposing && controls != null) + { + IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(es != null, "IExtenderProviderService not found"); + if (es != null) + { + es.RemoveExtenderProvider(this); + } + + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (eventHandlerService != null) + { + if (host != null) + { + host.RemoveService(typeof(IEventHandlerService)); + eventHandlerService = null; + } + } + + IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); + if (componentChangeService != null) + { + componentChangeService.ComponentRemoved -= new ComponentEventHandler(this.OnComponentRemoved); + } + + TypeDescriptor.Refreshed -= new RefreshEventHandler(OnComponentRefresh); + SystemEvents.DisplaySettingsChanged -= new EventHandler(this.OnSystemSettingChanged); + SystemEvents.InstalledFontsChanged -= new EventHandler(this.OnSystemSettingChanged); + SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(this.OnUserPreferenceChanged); + IMenuCommandService mcs = MenuService; + if (mcs != null) + { + Debug.Assert(_menucmdArrangeIcons != null, "Null Menu Command for ArrangeIcons"); + Debug.Assert(_menucmdLineupIcons != null, "Null Menu Command for LineupIcons"); + Debug.Assert(_menucmdLargeIcons != null, "Null Menu Command for LargeIcons"); + mcs.RemoveCommand(_menucmdArrangeIcons); + mcs.RemoveCommand(_menucmdLineupIcons); + mcs.RemoveCommand(_menucmdLargeIcons); + } + + if (privateCommandSet != null) + { + privateCommandSet.Dispose(); + // If we created a private command set, we also added a selection ui service to the host + if (host != null) + { + host.RemoveService(typeof(ISelectionUIService)); + } + } + selectionUISvc = null; + if (inheritanceUI != null) + { + inheritanceUI.Dispose(); + inheritanceUI = null; + } + + serviceProvider = null; + controls.Clear(); + controls = null; + if (glyphManager != null) + { + glyphManager.Dispose(); + glyphManager = null; + } + } + base.Dispose(disposing); } /// - /// Similar to GetNextControl on Control, this method returns the next - /// component in the tray, given a starting component. It will return - /// null if the end (or beginning, if forward is false) of the list - /// is encountered. + /// Similar to GetNextControl on Control, this method returns the next component in the tray, given a starting component. + /// It will return null if the end (or beginning, if forward is false) of the list is encountered. /// public IComponent GetNextComponent(IComponent component, bool forward) { - throw new NotImplementedException(SR.NotImplementedByDesign); + for (int i = 0; i < controls.Count; i++) + { + TrayControl control = (TrayControl)controls[i]; + if (control.Component == component) + { + int targetIndex = (forward ? i + 1 : i - 1); + if (targetIndex >= 0 && targetIndex < controls.Count) + { + return ((TrayControl)controls[targetIndex]).Component; + } + // Reached the end of the road. + return null; + } + } + // If we got here then the component isn't in our list. Prime the caller with either the first or the last. + if (controls.Count > 0) + { + int targetIndex = (forward ? 0 : controls.Count - 1); + return ((TrayControl)controls[targetIndex]).Component; + } + return null; } /// - /// Accessor method for the location extender property. We offer this extender - /// to all non-visual components. + /// Accessor method for the location extender property. We offer this extender to all non-visual components. /// [Category("Layout")] [Localizable(false)] @@ -227,12 +1179,22 @@ public IComponent GetNextComponent(IComponent component, bool forward) [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")] public Point GetLocation(IComponent receiver) { - throw new NotImplementedException(SR.NotImplementedByDesign); + // We shouldn't really end up here, but if we do.... + PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"]; + if (loc != null) + { + // In this case the component already had a Location property, and what the caller wants is the underlying components Location, not the tray location. Why? Because we now use TrayLocation. + return (Point)(loc.GetValue(receiver)); + } + else + { + // If the component didn't already have a Location property, then the caller really wants the tray location. Could be a 3rd party vendor. + return GetTrayLocation(receiver); + } } /// - /// Accessor method for the location extender property. We offer this extender - /// to all non-visual components. + /// Accessor method for the location extender property. We offer this extender to all non-visual components. /// [Category("Layout")] [Localizable(false)] @@ -241,166 +1203,1700 @@ public Point GetLocation(IComponent receiver) [DesignOnly(true)] public Point GetTrayLocation(IComponent receiver) { - throw new NotImplementedException(SR.NotImplementedByDesign); + Control c = TrayControl.FromComponent(receiver); + if (c == null) + { + Debug.Fail("Anything we're extending should have a component view."); + return new Point(); + } + + Point loc = c.Location; + Point autoScrollLoc = this.AutoScrollPosition; + return new Point(loc.X - autoScrollLoc.X, loc.Y - autoScrollLoc.Y); } /// - /// - /// Gets the requsted service type. - /// + /// Gets the requsted service type. /// protected override object GetService(Type serviceType) { - throw new NotImplementedException(SR.NotImplementedByDesign); + object service = null; + Debug.Assert(serviceProvider != null, "Trying to access services too late or too early."); + if (serviceProvider != null) + { + service = serviceProvider.GetService(serviceType); + } + return service; + } + + /// + /// Returns the traycontrol representing the IComponent. If no traycontrol is found, this returns null. This is used identify bounds for the DesignerAction UI. + /// + internal TrayControl GetTrayControlFromComponent(IComponent comp) + { + return TrayControl.FromComponent(comp); + } + + /// + /// Called from CommandSet's OnMenuPaste method. This will allow us to properly adjust the location of the components in the tray after we've incorreclty set them by deserializing the design time properties (and hence called SetValue(c, myBadLocation) on the location property). + /// + internal void UpdatePastePositions(ArrayList components) + { + foreach (TrayControl c in components) + { + if (!CanDisplayComponent(c.Component)) + { + return; + } + + if (mouseDropLocation == InvalidPoint) + { + Control prevCtl = null; + if (controls.Count > 1) + { + prevCtl = (Control)controls[controls.Count - 1]; + } + PositionInNextAutoSlot(c, prevCtl, true); + } + else + { + PositionControl(c); + } + c.BringToFront(); + } + } + + private void PositionControl(TrayControl c) + { + Debug.Assert(c.Visible, "TrayControl for " + c.Component + " should not be positioned"); + if (!autoArrange) + { + if (mouseDropLocation != InvalidPoint) + { + if (!c.Location.Equals(mouseDropLocation)) + c.Location = mouseDropLocation; + } + else + { + Control prevCtl = null; + if (controls.Count > 1) + { + // PositionControl can be called when all the controls have been added (from IOleDragClient.AddComponent), so we can't use the old way of looking up the previous control (prevCtl = controls[controls.Count - 2] + int index = controls.IndexOf(c); + Debug.Assert(index >= 1, "Got the wrong index, how could that be?"); + if (index >= 1) + { + prevCtl = (Control)controls[index - 1]; + } + } + PositionInNextAutoSlot(c, prevCtl, true); + } + } + else + { + if (mouseDropLocation != InvalidPoint) + { + RearrangeInAutoSlots(c, mouseDropLocation); + } + else + { + Control prevCtl = null; + if (controls.Count > 1) + { + int index = controls.IndexOf(c); + Debug.Assert(index >= 1, "Got the wrong index, how could that be?"); + if (index >= 1) + { + prevCtl = (Control)controls[index - 1]; + } + } + PositionInNextAutoSlot(c, prevCtl, true); + } + } + } + + internal void RearrangeInAutoSlots(Control c, Point pos) + { +#if DEBUG + int index = controls.IndexOf(c); + Debug.Assert(index != -1, "Add control to the list of controls before autoarranging.!!!"); + Debug.Assert(Visible == c.Visible, "TrayControl for " + ((TrayControl)c).Component + " should not be positioned"); +#endif // DEBUG + TrayControl tc = (TrayControl)c; + tc.Positioned = true; + tc.Location = pos; } /// - /// Returns true if the given componenent is being shown on the tray. + /// Returns true if the given componenent is being shown on the tray. /// public bool IsTrayComponent(IComponent comp) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (TrayControl.FromComponent(comp) == null) + { + return false; + } + + foreach (Control control in this.Controls) + { + if (control is TrayControl tc && tc.Component == comp) + { + return true; + } + } + return false; } protected override void OnMouseDoubleClick(MouseEventArgs e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + //give our glyphs first chance at this + if (glyphManager != null && glyphManager.OnMouseDoubleClick(e)) + { + //handled by a glyph - so don't send to the comp tray + return; + } + + base.OnDoubleClick(e); + if (!TabOrderActive) + { + OnLostCapture(); + IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(eps != null, "IEventBindingService not found"); + if (eps != null) + { + eps.ShowCode(); + } + } } /// - /// Inheriting classes should override this method to handle this event. - /// Call base.onGiveFeedback to send this event to any registered event listeners. + /// Inheriting classes should override this method to handle this event. + /// Call base.onGiveFeedback to send this event to any registered event listeners. /// protected override void OnGiveFeedback(GiveFeedbackEventArgs gfevent) { - throw new NotImplementedException(SR.NotImplementedByDesign); + base.OnGiveFeedback(gfevent); + GetOleDragHandler().DoOleGiveFeedback(gfevent); + } + + internal virtual OleDragDropHandler GetOleDragHandler() + { + if (oleDragDropHandler == null) + { + oleDragDropHandler = new TrayOleDragDropHandler(this.DragHandler, this.serviceProvider, this); + } + return oleDragDropHandler; + } + + internal virtual SelectionUIHandler DragHandler + { + get + { + if (dragHandler == null) + { + dragHandler = new TraySelectionUIHandler(this); + } + return dragHandler; + } + } + + private class TrayOleDragDropHandler : OleDragDropHandler + { + public TrayOleDragDropHandler(SelectionUIHandler selectionHandler, IServiceProvider serviceProvider, IOleDragClient client) : + base(selectionHandler, serviceProvider, client) + { + } + + protected override bool CanDropDataObject(IDataObject dataObj) + { + ICollection comps = null; + if (dataObj != null) + { + if (dataObj is ComponentDataObjectWrapper cdow) + { + ComponentDataObject cdo = (ComponentDataObject)cdow.InnerData; + comps = cdo.Components; + } + else + { + try + { + object serializationData = dataObj.GetData(OleDragDropHandler.DataFormat, true); + if (serializationData == null) + { + return false; + } + + IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService)); + if (ds == null) + { + return false; + } + comps = ds.Deserialize(serializationData); + } + catch (Exception e) + { + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + // we return false on any exception + } + } + } + + if (comps != null && comps.Count > 0) + { + foreach (object comp in comps) + { + if (comp is Point) + { + continue; + } + if (comp is Control || !(comp is IComponent)) + { + return false; + } + } + return true; + } + return false; + } + } + + private class TraySelectionUIHandler : SelectionUIHandler + { + private ComponentTray _tray; + private Size _snapSize = Size.Empty; + + public TraySelectionUIHandler(ComponentTray tray) + { + this._tray = tray; + _snapSize = new Size(); + } + + public override bool BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY) + { + bool value = base.BeginDrag(components, rules, initialX, initialY); + _tray.SuspendLayout(); + return value; + } + + public override void EndDrag(object[] components, bool cancel) + { + base.EndDrag(components, cancel); + _tray.ResumeLayout(); + } + + protected override IComponent GetComponent() => _tray; + + protected override Control GetControl() => _tray; + + protected override Control GetControl(IComponent component) => TrayControl.FromComponent(component); + + protected override Size GetCurrentSnapSize() => _snapSize; + + protected override object GetService(Type serviceType) => _tray.GetService(serviceType); + + protected override bool GetShouldSnapToGrid() => false; + + public override Rectangle GetUpdatedRect(Rectangle originalRect, Rectangle dragRect, bool updateSize) => dragRect; + + public override void SetCursor() => _tray.OnSetCursor(); } /// - /// Called in response to a drag drop for OLE drag and drop. Here we - /// drop a toolbox component on our parent control. + /// Called in response to a drag drop for OLE drag and drop. Here we drop a toolbox component on our parent control. /// protected override void OnDragDrop(DragEventArgs de) { - throw new NotImplementedException(SR.NotImplementedByDesign); + // This will be used once during PositionComponent to place the component at the drop point. It is automatically set to null afterwards, so further components appear after the first one dropped. + mouseDropLocation = PointToClient(new Point(de.X, de.Y)); + autoScrollPosBeforeDragging = this.AutoScrollPosition;//save the scroll position + if (mouseDragTool != null) + { + ToolboxItem tool = mouseDragTool; + mouseDragTool = null; + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(GetService(typeof(IDesignerHost)) != null, "IDesignerHost not found"); + try + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + IDesigner designer = host.GetDesigner(host.RootComponent); + if (designer is IToolboxUser itu) + { + itu.ToolPicked(tool); + } + else + { + CreateComponentFromTool(tool); + } + } + catch (Exception e) + { + DisplayError(e); + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + } + de.Effect = DragDropEffects.Copy; + } + else + { + GetOleDragHandler().DoOleDragDrop(de); + } + mouseDropLocation = InvalidPoint; + ResumeLayout(); } /// - /// Called in response to a drag enter for OLE drag and drop. + /// Called in response to a drag enter for OLE drag and drop. /// protected override void OnDragEnter(DragEventArgs de) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (!TabOrderActive) + { + SuspendLayout(); + if (toolboxService == null) + { + toolboxService = (IToolboxService)GetService(typeof(IToolboxService)); + } + + OleDragDropHandler dragDropHandler = GetOleDragHandler(); + object[] dragComps = dragDropHandler.GetDraggingObjects(de); + // Only assume the items came from the ToolBox if dragComps == null + if (toolboxService != null && dragComps == null) + { + mouseDragTool = toolboxService.DeserializeToolboxItem(de.Data, (IDesignerHost)GetService(typeof(IDesignerHost))); + } + + if (mouseDragTool != null) + { + Debug.Assert(0 != (int)(de.AllowedEffect & (DragDropEffects.Move | DragDropEffects.Copy)), "DragDropEffect.Move | .Copy isn't allowed?"); + if ((int)(de.AllowedEffect & DragDropEffects.Move) != 0) + { + de.Effect = DragDropEffects.Move; + } + else + { + de.Effect = DragDropEffects.Copy; + } + } + else + { + dragDropHandler.DoOleDragEnter(de); + } + } } /// - /// Called when a drag-drop operation leaves the control designer view + /// Called when a drag-drop operation leaves the control designer view /// protected override void OnDragLeave(EventArgs e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + mouseDragTool = null; + GetOleDragHandler().DoOleDragLeave(); + ResumeLayout(); } /// - /// Called when a drag drop object is dragged over the control designer view + /// Called when a drag drop object is dragged over the control designer view /// protected override void OnDragOver(DragEventArgs de) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (mouseDragTool != null) + { + Debug.Assert(0 != (int)(de.AllowedEffect & DragDropEffects.Copy), "DragDropEffect.Move isn't allowed?"); + de.Effect = DragDropEffects.Copy; + } + else + { + GetOleDragHandler().DoOleDragOver(de); + } } - /// /// - /// Forces the layout of any docked or anchored child controls. + /// Forces the layout of any docked or anchored child controls. /// protected override void OnLayout(LayoutEventArgs levent) { - throw new NotImplementedException(SR.NotImplementedByDesign); + DoAutoArrange(false); + // make sure selection service redraws + Invalidate(true); + base.OnLayout(levent); } /// - /// This is called when we lose capture. Here we get rid of any - /// rubber band we were drawing. You should put any cleanup - /// code in here. + /// This is called when we lose capture. Here we get rid of any rubber band we were drawing. + /// You should put any cleanup code in here. /// protected virtual void OnLostCapture() { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (mouseDragStart != InvalidPoint) + { + Cursor.Clip = Rectangle.Empty; + if (mouseDragEnd != InvalidPoint) + { + DrawRubber(mouseDragStart, mouseDragEnd); + mouseDragEnd = InvalidPoint; + } + mouseDragStart = InvalidPoint; + } + } + + private void DrawRubber(Point start, Point end) + { + mouseDragWorkspace.X = Math.Min(start.X, end.X); + mouseDragWorkspace.Y = Math.Min(start.Y, end.Y); + mouseDragWorkspace.Width = Math.Abs(end.X - start.X); + mouseDragWorkspace.Height = Math.Abs(end.Y - start.Y); + mouseDragWorkspace = RectangleToScreen(mouseDragWorkspace); + ControlPaint.DrawReversibleFrame(mouseDragWorkspace, BackColor, FrameStyle.Dashed); } /// - /// Inheriting classes should override this method to handle this event. - /// Call base.onMouseDown to send this event to any registered event listeners. + /// Inheriting classes should override this method to handle this event. + /// Call base.onMouseDown to send this event to any registered event listeners. /// protected override void OnMouseDown(MouseEventArgs e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + //give our glyphs first chance at this + if (glyphManager != null && glyphManager.OnMouseDown(e)) + { + //handled by a glyph - so don't send to the comp tray + return; + } + base.OnMouseDown(e); + if (!TabOrderActive) + { + if (toolboxService == null) + { + toolboxService = (IToolboxService)GetService(typeof(IToolboxService)); + } + + FocusDesigner(); + if (e.Button == MouseButtons.Left && toolboxService != null) + { + ToolboxItem tool = toolboxService.GetSelectedToolboxItem((IDesignerHost)GetService(typeof(IDesignerHost))); + if (tool != null) + { + // mouseDropLocation is checked in PositionControl, which should get called as a result of adding a new component. This allows us to set the position without flickering, while still providing support for auto layout if the control was double clicked or added through extensibility. + mouseDropLocation = new Point(e.X, e.Y); + try + { + CreateComponentFromTool(tool); + toolboxService.SelectedToolboxItemUsed(); + } + catch (Exception ex) + { + DisplayError(ex); + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + mouseDropLocation = InvalidPoint; + return; + } + } + + // If it is the left button, start a rubber band drag to laso controls. + if (e.Button == MouseButtons.Left) + { + mouseDragStart = new Point(e.X, e.Y); + Capture = true; + Cursor.Clip = RectangleToScreen(ClientRectangle); + } + else + { + try + { + ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(ss != null, "ISelectionService not found"); + if (ss != null) + { + ss.SetSelectedComponents(new object[] { mainDesigner.Component }); + } + } + catch (Exception ex) + { + // nothing we can really do here; just eat it. + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + } + } } /// - /// Inheriting classes should override this method to handle this event. - /// Call base.onMouseMove to send this event to any registered event listeners. + /// Inheriting classes should override this method to handle this event. + /// Call base.onMouseMove to send this event to any registered event listeners. /// protected override void OnMouseMove(MouseEventArgs e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + //give our glyphs first chance at this + if (glyphManager != null && glyphManager.OnMouseMove(e)) + { + //handled by a glyph - so don't send to the comp tray + return; + } + + base.OnMouseMove(e); + // If we are dragging, then draw our little rubber band. + // + if (mouseDragStart != InvalidPoint) + { + if (mouseDragEnd != InvalidPoint) + { + DrawRubber(mouseDragStart, mouseDragEnd); + } + else + { + mouseDragEnd = new Point(0, 0); + } + mouseDragEnd.X = e.X; + mouseDragEnd.Y = e.Y; + DrawRubber(mouseDragStart, mouseDragEnd); + } } /// - /// Inheriting classes should override this method to handle this event. - /// Call base.onMouseUp to send this event to any registered event listeners. + /// Inheriting classes should override this method to handle this event. + /// Call base.onMouseUp to send this event to any registered event listeners. /// protected override void OnMouseUp(MouseEventArgs e) { - throw new NotImplementedException(SR.NotImplementedByDesign); + //give our glyphs first chance at this + if (glyphManager != null && glyphManager.OnMouseUp(e)) + { + //handled by a glyph - so don't send to the comp tray + return; + } + if (mouseDragStart != InvalidPoint && e.Button == MouseButtons.Left) + { + object[] comps = null; + Capture = false; + Cursor.Clip = Rectangle.Empty; + if (mouseDragEnd != InvalidPoint) + { + DrawRubber(mouseDragStart, mouseDragEnd); + Rectangle rect = new Rectangle(); + rect.X = Math.Min(mouseDragStart.X, e.X); + rect.Y = Math.Min(mouseDragStart.Y, e.Y); + rect.Width = Math.Abs(e.X - mouseDragStart.X); + rect.Height = Math.Abs(e.Y - mouseDragStart.Y); + comps = GetComponentsInRect(rect); + mouseDragEnd = InvalidPoint; + } + else + { + comps = new object[0]; + } + + if (comps.Length == 0) + { + comps = new object[] { mainDesigner.Component }; + } + + try + { + ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(ss != null, "ISelectionService not found"); + if (ss != null) + { + ss.SetSelectedComponents(comps); + } + } + catch (Exception ex) + { + // nothing we can really do here; just eat it. + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + mouseDragStart = InvalidPoint; + } + base.OnMouseUp(e); + } + private object[] GetComponentsInRect(Rectangle rect) + { + ArrayList list = new ArrayList(); + int controlCount = Controls.Count; + for (int i = 0; i < controlCount; i++) + { + Control child = Controls[i]; + Rectangle bounds = child.Bounds; + if (child is TrayControl tc && bounds.IntersectsWith(rect)) + { + list.Add(tc.Component); + } + } + return list.ToArray(); } protected override void OnPaint(PaintEventArgs pe) { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (_fResetAmbient || _fSelectionChanged) + { + _fResetAmbient = false; + _fSelectionChanged = false; + IUIService uiService = (IUIService)GetService(typeof(IUIService)); + if (uiService != null) + { + Color styleColor; + if (uiService.Styles["ArtboardBackground"] is Color) + { + styleColor = (Color)uiService.Styles["ArtboardBackground"]; + } + //Can't use 'as' here since Color is a value type + else if (uiService.Styles["VsColorDesignerTray"] is Color) + { + styleColor = (Color)uiService.Styles["VsColorDesignerTray"]; + } + else if (uiService.Styles["HighlightColor"] is Color) + { + // Since v1, we have had code here that checks for HighlightColor, so some hosts (like WinRes) have been setting it. If VsColorDesignerTray isn't present, we look for HighlightColor for backward compat. + styleColor = (Color)uiService.Styles["HighlightColor"]; + } + else + { + //No style color provided? Let's pick a default. + styleColor = SystemColors.Info; + } + + BackColor = styleColor; + Font = (Font)uiService.Styles["DialogFont"]; + + foreach (Control ctl in controls) + { + ctl.BackColor = styleColor; + ctl.ForeColor = ForeColor; + } + } + } + + base.OnPaint(pe); + Graphics gr = pe.Graphics; + // Now, if we have a selection, paint it + if (selectedObjects != null) + { + bool first = true;//indicates the first iteration of our foreach loop + HatchBrush selectionBorderBrush; + if (SystemInformation.HighContrast) + { + selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.HighlightText, Color.Transparent); + } + else + { + selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.ControlDarkDark, Color.Transparent); + } + + try + { + foreach (object o in selectedObjects) + { + Control c = ((IOleDragClient)this).GetControlForComponent(o); + if (c != null && c.Visible) + { + Rectangle innerRect = c.Bounds; + if (SystemInformation.HighContrast) + { + c.ForeColor = SystemColors.HighlightText; + c.BackColor = SystemColors.Highlight; + } + + NoResizeHandleGlyph glyph = new NoResizeHandleGlyph(innerRect, SelectionRules.None, first, null); + + gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Top)); + gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Bottom)); + gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Left)); + gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Right)); + + // Need to draw this one last + DesignerUtils.DrawNoResizeHandle(gr, glyph.Bounds, first, glyph); + } + first = false; + } + } + finally + { + if (selectionBorderBrush != null) + { + selectionBorderBrush.Dispose(); + } + } + } + //paint any glyphs + if (glyphManager != null) + { + glyphManager.OnPaintGlyphs(pe); + } + } + + internal class NoResizeHandleGlyph : SelectionGlyphBase + { + private bool _isPrimary = false; + internal NoResizeHandleGlyph(Rectangle controlBounds, SelectionRules selRules, bool primarySelection, Behavior.Behavior behavior) : base(behavior) + { + _isPrimary = primarySelection; + hitTestCursor = Cursors.Default; + rules = SelectionRules.None; + if ((selRules & SelectionRules.Moveable) != 0) + { + rules = SelectionRules.Moveable; + hitTestCursor = Cursors.SizeAll; + } + + // The handle is always upperleft + bounds = new Rectangle(controlBounds.X - DesignerUtils.NORESIZEHANDLESIZE, controlBounds.Y - DesignerUtils.NORESIZEHANDLESIZE, DesignerUtils.NORESIZEHANDLESIZE, DesignerUtils.NORESIZEHANDLESIZE); + hitBounds = bounds; + + } + public override void Paint(PaintEventArgs pe) + { + DesignerUtils.DrawNoResizeHandle(pe.Graphics, bounds, _isPrimary, this); + } + } /// - /// Sets the cursor. You may override this to set your own - /// cursor. + /// Sets the cursor. You may override this to set your own cursor. /// protected virtual void OnSetCursor() { - throw new NotImplementedException(SR.NotImplementedByDesign); + if (toolboxService == null) + { + toolboxService = (IToolboxService)GetService(typeof(IToolboxService)); + } + + if (toolboxService == null || !toolboxService.SetCursor()) + { + Cursor.Current = Cursors.Default; + } } /// - /// Removes a component from the tray. + /// Removes a component from the tray. /// public virtual void RemoveComponent(IComponent component) { - throw new NotImplementedException(SR.NotImplementedByDesign); + TrayControl c = TrayControl.FromComponent(component); + if (c != null) + { + try + { + InheritanceAttribute attr = c.InheritanceAttribute; + if (attr.InheritanceLevel != InheritanceLevel.NotInherited && inheritanceUI != null) + { + inheritanceUI.RemoveInheritedControl(c); + } + + if (controls != null) + { + int index = controls.IndexOf(c); + if (index != -1) + controls.RemoveAt(index); + } + } + finally + { + c.Dispose(); + } + } } /// - /// Accessor method for the location extender property. We offer this extender - /// to all non-visual components. + /// Accessor method for the location extender property. We offer this extender to all non-visual components. /// public void SetLocation(IComponent receiver, Point location) { - throw new NotImplementedException(SR.NotImplementedByDesign); + // This really should only be called when we are loading. + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null && host.Loading) + { + // If we are loading, and we get called here, that's because we have provided the extended Location property. In this case we are loading an old project, and what we are really setting is the tray location. + SetTrayLocation(receiver, location); + } + else + { + // we are not loading + PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"]; + if (loc != null) + { + // so if the component already had the Location property, what the caller wants is really the underlying component's Location property. + loc.SetValue(receiver, location); + } + else + { + // if the component didn't have a Location property, then the caller really wanted the tray location. + SetTrayLocation(receiver, location); + } + } } /// - /// Accessor method for the location extender property. We offer this extender - /// to all non-visual components. + /// Accessor method for the location extender property. We offer this extender to all non-visual components. /// public void SetTrayLocation(IComponent receiver, Point location) { - throw new NotImplementedException(SR.NotImplementedByDesign); + TrayControl c = TrayControl.FromComponent(receiver); + if (c == null) + { + Debug.Fail("Anything we're extending should have a component view."); + return; + } + + if (c.Parent == this) + { + Point autoScrollLoc = this.AutoScrollPosition; + location = new Point(location.X + autoScrollLoc.X, location.Y + autoScrollLoc.Y); + + if (c.Visible) + { + RearrangeInAutoSlots(c, location); + } + } + else if (!c.Location.Equals(location)) + { + c.Location = location; + c.Positioned = true; + } } /// - /// We override our base class's WndProc to monitor certain messages. + /// We override our base class's WndProc to monitor certain messages. /// protected override void WndProc(ref Message m) { - throw new NotImplementedException(SR.NotImplementedByDesign); + switch (m.Msg) + { + case NativeMethods.WM_CANCELMODE: + // When we get cancelmode (i.e. you tabbed away to another window) then we want to cancel any pending drag operation! + OnLostCapture(); + break; + case NativeMethods.WM_SETCURSOR: + OnSetCursor(); + return; + case NativeMethods.WM_HSCROLL: + case NativeMethods.WM_VSCROLL: + // When we scroll, we reposition a control without causing a property change event. Therefore, we must tell the selection UI service to sync itself. + base.WndProc(ref m); + if (selectionUISvc != null) + { + selectionUISvc.SyncSelection(); + } + return; + + case NativeMethods.WM_STYLECHANGED: + // When the scroll bars first appear, we need to invalidate so we properly paint our grid. + Invalidate(); + break; + case NativeMethods.WM_CONTEXTMENU: + // Pop a context menu for the composition designer. + int x = NativeMethods.Util.SignedLOWORD(unchecked((int)(long)m.LParam)); + int y = NativeMethods.Util.SignedHIWORD(unchecked((int)(long)m.LParam)); + if (x == -1 && y == -1) + { + // for shift-F10 + Point mouse = Control.MousePosition; + x = mouse.X; + y = mouse.Y; + } + OnContextMenu(x, y, true); + break; + case NativeMethods.WM_NCHITTEST: + if (glyphManager != null) + { + // Get a hit test on any glyhs that we are managing this way - we know where to route appropriate messages + Point pt = new Point((short)NativeMethods.Util.LOWORD(unchecked((int)(long)m.LParam)), (short)NativeMethods.Util.HIWORD(unchecked((int)(long)m.LParam))); + NativeMethods.POINT pt1 = new NativeMethods.POINT + { + x = 0, + y = 0 + }; + NativeMethods.MapWindowPoints(IntPtr.Zero, Handle, pt1, 1); + pt.Offset(pt1.x, pt1.y); + glyphManager.GetHitTest(pt); + } + base.WndProc(ref m); + break; + default: + base.WndProc(ref m); + break; + } + } + + private void OnContextMenu(int x, int y, bool useSelection) + { + if (!TabOrderActive) + { + Capture = false; + IMenuCommandService mcs = MenuService; + if (mcs != null) + { + Capture = false; + Cursor.Clip = Rectangle.Empty; + ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService)); + if (useSelection && s != null && !(1 == s.SelectionCount && s.PrimarySelection == mainDesigner.Component)) + { + mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, x, y); + } + else + { + mcs.ShowContextMenu(MenuCommands.ComponentTrayMenu, x, y); + } + } + } + } + + internal void FocusDesigner() + { + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null && host.RootComponent != null) + { + if (host.GetDesigner(host.RootComponent) is IRootDesigner rd) + { + ViewTechnology[] techs = rd.SupportedTechnologies; + if (techs.Length > 0) + { + if (rd.GetView(techs[0]) is Control view) + { + view.Focus(); + } + } + } + } + } + internal class AutoArrangeComparer : IComparer + { + int IComparer.Compare(object o1, object o2) + { + Debug.Assert(o1 != null && o2 != null, "Null objects sent for comparison!!!"); + + Point tcLoc1 = ((Control)o1).Location; + Point tcLoc2 = ((Control)o2).Location; + int height = ((Control)o1).Height / 2; + + // If they are at the same location, they are equal. + if (tcLoc1.X == tcLoc2.X && tcLoc1.Y == tcLoc2.Y) + { + return 0; + } + + // Is the first control lower than the 2nd... + if (tcLoc1.Y + height <= tcLoc2.Y) + return -1; + + // Is the 2nd control lower than the first... + if (tcLoc2.Y + height <= tcLoc1.Y) + return 1; + + // Which control is left of the other... + return ((tcLoc1.X <= tcLoc2.X) ? -1 : 1); + } + } + + /// + /// The tray control is the UI we show for each component in the tray. + /// + internal class TrayControl : Control + { + // Values that define this tray control + private readonly IComponent _component; // the component this control is representing + private Image _toolboxBitmap; // the bitmap used to represent the component + private int _cxIcon; // the dimensions of the bitmap + private int _cyIcon; // the dimensions of the bitmap + + private readonly InheritanceAttribute _inheritanceAttribute; + + // Services that we use often enough to cache. + private readonly ComponentTray _tray; + + // transient values that are used during mouse drags + private Point _mouseDragLast = InvalidPoint; // the last position of the mouse during a drag. + private bool _mouseDragMoved; // has the mouse been moved during this drag? + private bool _ctrlSelect = false; // was the ctrl key down on the mouse down? + private bool _positioned = false; // Have we given this control an explicit location yet? + + private const int WhiteSpace = 5; + private readonly int _borderWidth; + + internal bool _fRecompute = false; // This flag tells the TrayControl that it needs to retrieve the font and the background color before painting. + + /// + /// Creates a new TrayControl based on the component. + /// + public TrayControl(ComponentTray tray, IComponent component) + { + _tray = tray; + _component = component; + + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.Selectable, false); + _borderWidth = SystemInformation.BorderSize.Width; + + UpdateIconInfo(); + + IComponentChangeService cs = (IComponentChangeService)tray.GetService(typeof(IComponentChangeService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(cs != null, "IComponentChangeService not found"); + if (cs != null) + { + cs.ComponentRename += new ComponentRenameEventHandler(OnComponentRename); + } + + ISite site = component.Site; + string name = null; + + if (site != null) + { + name = site.Name; + + IDictionaryService ds = (IDictionaryService)site.GetService(typeof(IDictionaryService)); + Debug.Assert(ds != null, "ComponentTray relies on IDictionaryService, which is not available."); + if (ds != null) + { + ds.SetValue(GetType(), this); + } + } + + if (name == null) + { + // We always want name to have something in it, so we default to the class name. This way the design instance contains something semi-intuitive if we don't have a site. + name = component.GetType().Name; + } + Text = name; + _inheritanceAttribute = (InheritanceAttribute)TypeDescriptor.GetAttributes(component)[typeof(InheritanceAttribute)]; + TabStop = false; + } + + /// + /// Retrieves the compnent this control is representing. + /// + public IComponent Component + { + get => _component; + } + + public override Font Font + { + get => _tray.Font; + } + + public InheritanceAttribute InheritanceAttribute + { + get => _inheritanceAttribute; + } + + public bool Positioned + { + get => _positioned; + set => _positioned = value; + } + + /// + /// Adjusts the size of the control based on the contents. + /// + // CONSIDER: this method gets called three or four times per component, and is even reentrant (CreateGraphics can force handle creation, and OnCreateHandle calls this method). + // There's probably a better way to do this, but since this doesn't seem to be on the critical path, I'm not going to lose sleep over it. + private void AdjustSize() + { + // CONSIDER: this forces handle creation. Can we delay this calculation? + Graphics gr = CreateGraphics(); + + try + { + Size sz = Size.Ceiling(gr.MeasureString(Text, Font)); + + Rectangle rc = Bounds; + + if (_tray.ShowLargeIcons) + { + rc.Width = Math.Max(_cxIcon, sz.Width) + 4 * _borderWidth + 2 * WhiteSpace; + rc.Height = _cyIcon + 2 * WhiteSpace + sz.Height + 4 * _borderWidth; + } + else + { + rc.Width = _cxIcon + sz.Width + 4 * _borderWidth + 2 * WhiteSpace; + rc.Height = Math.Max(_cyIcon, sz.Height) + 4 * _borderWidth; + } + + Bounds = rc; + Invalidate(); + } + + finally + { + if (gr != null) + { + gr.Dispose(); + } + } + + if (_tray.glyphManager != null) + { + _tray.glyphManager.UpdateLocation(this); + } + } + + protected override AccessibleObject CreateAccessibilityInstance() + { + return new TrayControlAccessibleObject(this, _tray); + } + + private class TrayControlAccessibleObject : ControlAccessibleObject + { + readonly ComponentTray _tray; + + public TrayControlAccessibleObject(TrayControl owner, ComponentTray tray) : base(owner) + { + _tray = tray; + } + + private IComponent Component + { + get => ((TrayControl)Owner).Component; + } + + public override AccessibleStates State + { + get + { + AccessibleStates state = base.State; + ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService)); + if (s != null) + { + if (s.GetComponentSelected(Component)) + { + state |= AccessibleStates.Selected; + } + if (s.PrimarySelection == Component) + { + state |= AccessibleStates.Focused; + } + } + return state; + } + } + } + + /// + /// Destroys this control. Views automatically destroy themselves when they are removed from the design container. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + ISite site = _component.Site; + if (site != null) + { + IComponentChangeService cs = (IComponentChangeService)site.GetService(typeof(IComponentChangeService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(cs != null, "IComponentChangeService not found"); + if (cs != null) + { + cs.ComponentRename -= new ComponentRenameEventHandler(OnComponentRename); + } + + IDictionaryService ds = (IDictionaryService)site.GetService(typeof(IDictionaryService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(ds != null, "IDictionaryService not found"); + if (ds != null) + { + ds.SetValue(typeof(TrayControl), null); + } + } + } + + base.Dispose(disposing); + } + + /// + /// Retrieves the tray control object for the given component. + /// + public static TrayControl FromComponent(IComponent component) + { + TrayControl c = null; + + if (component == null) + { + return null; + } + + ISite site = component.Site; + if (site != null) + { + IDictionaryService ds = (IDictionaryService)site.GetService(typeof(IDictionaryService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(ds != null, "IDictionaryService not found"); + if (ds != null) + { + c = (TrayControl)ds.GetValue(typeof(TrayControl)); + } + } + + return c; + } + + /// + /// Delegate that is called in response to a name change. Here we update our own stashed version of the name, recalcuate our size and repaint. + /// + private void OnComponentRename(object sender, ComponentRenameEventArgs e) + { + if (e.Component == _component) + { + Text = e.NewName; + AdjustSize(); + } + } + + /// + /// Overrides handle creation notification for a control. Here we just ensure that we're the proper size. + /// + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + AdjustSize(); + } + + /// + /// Called in response to a double-click of the left mouse button. The default behavior here calls onDoubleClick on IMouseHandler + /// + protected override void OnDoubleClick(EventArgs e) + { + base.OnDoubleClick(e); + + if (!_tray.TabOrderActive) + { + IDesignerHost host = (IDesignerHost)_tray.GetService(typeof(IDesignerHost)); + Debug.Assert(host != null, "Component tray does not have access to designer host."); + if (host != null) + { + _mouseDragLast = InvalidPoint; + + Capture = false; + + // We try to get a designer for the component and let it view the event. If this fails, then we'll try to do it ourselves. + IDesigner designer = host.GetDesigner(_component); + + if (designer == null) + { + ViewDefaultEvent(_component); + } + else + { + designer.DoDefaultAction(); + } + } + } + } + + /// + /// This creates a method signature in the source code file for the default event on the component and navigates the user's cursor to that location. + /// + public virtual void ViewDefaultEvent(IComponent component) + { + EventDescriptor defaultEvent = TypeDescriptor.GetDefaultEvent(component); + PropertyDescriptor defaultPropEvent = null; + bool eventChanged = false; + + IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService)); + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(eps != null, "IEventBindingService not found"); + if (eps != null) + { + defaultPropEvent = eps.GetEventProperty(defaultEvent); + } + + // If we couldn't find a property for this event, or if the property is read only, then abort and just show the code. + if (defaultPropEvent == null || defaultPropEvent.IsReadOnly) + { + if (eps != null) + { + eps.ShowCode(); + } + return; + } + + string handler = (string)defaultPropEvent.GetValue(component); + + // If there is no handler set, set one now. + if (handler == null) + { + eventChanged = true; + handler = eps.CreateUniqueMethodName(component, defaultEvent); + } + + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + DesignerTransaction trans = null; + try + { + if (host != null) + { + trans = host.CreateTransaction(string.Format(SR.WindowsFormsAddEvent, defaultEvent.Name)); + } + + // Save the new value... BEFORE navigating to it! + if (eventChanged && defaultPropEvent != null) + { + defaultPropEvent.SetValue(component, handler); + } + eps.ShowCode(component, defaultEvent); + } + finally + { + if (trans != null) + { + trans.Commit(); + } + } + } + + /// + /// Terminates our drag operation. + /// + private void OnEndDrag(bool cancel) + { + _mouseDragLast = InvalidPoint; + + if (!_mouseDragMoved) + { + if (_ctrlSelect) + { + ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService)); + if (sel != null) + { + sel.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary); + } + _ctrlSelect = false; + } + return; + } + _mouseDragMoved = false; + _ctrlSelect = false; + + Capture = false; + OnSetCursor(); + + // And now finish the drag. + Debug.Assert(_tray.selectionUISvc != null, "We shouldn't be able to begin a drag without this"); + if (_tray.selectionUISvc != null && _tray.selectionUISvc.Dragging) + { + _tray.selectionUISvc.EndDrag(cancel); + } + } + + /// + /// Called when the mouse button is pressed down. Here, we provide drag support for the component. + /// + protected override void OnMouseDown(MouseEventArgs me) + { + base.OnMouseDown(me); + + if (!_tray.TabOrderActive) + { + _tray.FocusDesigner(); + + // If this is the left mouse button, then begin a drag. + if (me.Button == MouseButtons.Left) + { + Capture = true; + _mouseDragLast = PointToScreen(new Point(me.X, me.Y)); + + // If the CTRL key isn't down, select this component, otherwise, we wait until the mouse up + // Make sure the component is selected + _ctrlSelect = NativeMethods.GetKeyState((int)Keys.ControlKey) != 0; + + if (!_ctrlSelect) + { + ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService)); + + // Make sure the component is selected + // + if (CompModSwitches.CommonDesignerServices.Enabled) + Debug.Assert(sel != null, "ISelectionService not found"); + + if (sel != null) + { + sel.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary); + } + } + } + } + } + + /// + /// Called when the mouse is moved over the component. We update our drag information here if we're dragging the component around. + /// + protected override void OnMouseMove(MouseEventArgs me) + { + base.OnMouseMove(me); + if (_mouseDragLast == InvalidPoint) + { + return; + } + + if (!_mouseDragMoved) + { + Size minDrag = SystemInformation.DragSize; + Size minDblClick = SystemInformation.DoubleClickSize; + minDrag.Width = Math.Max(minDrag.Width, minDblClick.Width); + minDrag.Height = Math.Max(minDrag.Height, minDblClick.Height); + + // we have to make sure the mouse moved farther than the minimum drag distance before we actually start the drag + Point newPt = PointToScreen(new Point(me.X, me.Y)); + if (_mouseDragLast == InvalidPoint || + (Math.Abs(_mouseDragLast.X - newPt.X) < minDrag.Width && + Math.Abs(_mouseDragLast.Y - newPt.Y) < minDrag.Height)) + { + return; + } + else + { + _mouseDragMoved = true; + // we're on the move, so we're not in a ctrlSelect + _ctrlSelect = false; + } + } + + try + { + // Make sure the component is selected + ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService)); + if (sel != null) + { + sel.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary); + } + + // Notify the selection service that all the components are in the "mouse down" mode. + if (_tray.selectionUISvc != null && _tray.selectionUISvc.BeginDrag(SelectionRules.Visible | SelectionRules.Moveable, _mouseDragLast.X, _mouseDragLast.Y)) + { + OnSetCursor(); + } + } + finally + { + _mouseDragMoved = false; + _mouseDragLast = InvalidPoint; + } + } + + /// + /// Called when the mouse button is released. Here, we finish our drag + /// if one was started. + /// + protected override void OnMouseUp(MouseEventArgs me) + { + base.OnMouseUp(me); + OnEndDrag(false); + } + + /// + /// Called when we are to display our context menu for this component. + /// + private void OnContextMenu(int x, int y) + { + if (!_tray.TabOrderActive) + { + Capture = false; + + // Ensure that this component is selected. + // + ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService)); + if (s != null && !s.GetComponentSelected(_component)) + { + s.SetSelectedComponents(new object[] { _component }, SelectionTypes.Replace); + } + + IMenuCommandService mcs = _tray.MenuService; + if (mcs != null) + { + Capture = false; + Cursor.Clip = Rectangle.Empty; + mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, x, y); + } + } + } + + /// + /// Painting for our control. + /// + protected override void OnPaint(PaintEventArgs e) + { + if (_fRecompute) + { + _fRecompute = false; + UpdateIconInfo(); + } + + base.OnPaint(e); + Rectangle rc = ClientRectangle; + + rc.X += WhiteSpace + _borderWidth; + rc.Y += _borderWidth; + rc.Width -= (2 * _borderWidth + WhiteSpace); + rc.Height -= 2 * _borderWidth; + + StringFormat format = new StringFormat(); + Brush foreBrush = new SolidBrush(ForeColor); + + try + { + format.Alignment = StringAlignment.Center; + if (_tray.ShowLargeIcons) + { + if (null != _toolboxBitmap) + { + int x = rc.X + (rc.Width - _cxIcon) / 2; + int y = rc.Y + WhiteSpace; + e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(x, y, _cxIcon, _cyIcon)); + } + + rc.Y += (_cyIcon + WhiteSpace); + rc.Height -= _cyIcon; + e.Graphics.DrawString(Text, Font, foreBrush, rc, format); + } + else + { + if (null != _toolboxBitmap) + { + int y = rc.Y + (rc.Height - _cyIcon) / 2; + e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(rc.X, y, _cxIcon, _cyIcon)); + } + + rc.X += (_cxIcon + _borderWidth); + rc.Width -= _cxIcon; + rc.Y += 3; + e.Graphics.DrawString(Text, Font, foreBrush, rc); + } + } + + finally + { + if (format != null) + { + format.Dispose(); + } + if (foreBrush != null) + { + foreBrush.Dispose(); + } + } + + // If this component is being inherited, paint it as such + if (!InheritanceAttribute.NotInherited.Equals(_inheritanceAttribute)) + { + InheritanceUI iui = _tray.InheritanceUI; + if (iui != null) + { + e.Graphics.DrawImage(iui.InheritanceGlyph, 0, 0); + } + } + } + + internal void UpdateIconInfo() + { + ToolboxBitmapAttribute attr = (ToolboxBitmapAttribute)TypeDescriptor.GetAttributes(_component)[typeof(ToolboxBitmapAttribute)]; + if (attr != null) + { + _toolboxBitmap = attr.GetImage(_component, _tray.ShowLargeIcons); + } + + // Get the size of the bitmap so we can size our component correctly. + if (null == _toolboxBitmap) + { + _cxIcon = 0; + _cyIcon = SystemInformation.IconSize.Height; + } + else + { + Size sz = _toolboxBitmap.Size; + _cxIcon = sz.Width; + _cyIcon = sz.Height; + } + + AdjustSize(); + } + + /// + /// Overrides control's FontChanged. Here we re-adjust our size if the font changes. + /// + protected override void OnFontChanged(EventArgs e) + { + AdjustSize(); + base.OnFontChanged(e); + } + + /// + /// Overrides control's LocationChanged. Here, we make sure that any glyphs associated with us are also relocated. + /// + protected override void OnLocationChanged(EventArgs e) + { + if (_tray.glyphManager != null) + { + _tray.glyphManager.UpdateLocation(this); + } + } + + /// + /// Overrides control's TextChanged. Here we re-adjust our size if the font changes. + /// + protected override void OnTextChanged(EventArgs e) + { + AdjustSize(); + base.OnTextChanged(e); + } + + private void OnSetCursor() + { + // Check that the component is not locked. + PropertyDescriptor prop; + try + { + prop = TypeDescriptor.GetProperties(_component)["Locked"]; + } + catch (FileNotFoundException e) + { + // In case an unhandled exception was encountered, we don't want to leave the cursor with some strange shape + // Currently we have watson logs with FileNotFoundException only, so we are scoping the catch only to that type. + Cursor.Current = Cursors.Default; + Debug.Fail(e.Message); + return; + } + + if (prop != null && ((bool)prop.GetValue(_component)) == true) + { + Cursor.Current = Cursors.Default; + return; + } + + // Ask the tray to see if the tab order UI is not running. + if (_tray.TabOrderActive) + { + Cursor.Current = Cursors.Default; + return; + } + + if (_mouseDragMoved) + { + Cursor.Current = Cursors.Default; + } + else if (_mouseDragLast != InvalidPoint) + { + Cursor.Current = Cursors.Cross; + } + else + { + Cursor.Current = Cursors.SizeAll; + } + } } } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgs.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgs.cs new file mode 100644 index 00000000000..7c1af36b0b2 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgs.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + /// + /// Provides data for the event. + /// + internal class ContainerSelectorActiveEventArgs : EventArgs + { + private readonly object _component; + private readonly ContainerSelectorActiveEventArgsType _eventType; + + /// + /// Initializes a new instance of the 'ContainerSelectorActiveEventArgs' class. + /// + public ContainerSelectorActiveEventArgs(object component) : this(component, ContainerSelectorActiveEventArgsType.Mouse) + { + } + + /// + /// Initializes a new instance of the 'ContainerSelectorActiveEventArgs' class. + /// + public ContainerSelectorActiveEventArgs(object component, ContainerSelectorActiveEventArgsType eventType) + { + _component = component; + _eventType = eventType; + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgsType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgsType.cs new file mode 100644 index 00000000000..ad86ebeb6c0 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventArgsType.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + /// + /// Specifies IDs for containers of certain event types. + /// + internal enum ContainerSelectorActiveEventArgsType + { + /// + /// Indicates the container of the active event was the contextmenu. + /// + Contextmenu = 1, + /// + /// Indicates the container of the active event was the mouse. + /// + Mouse = 2, + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventHandler.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventHandler.cs new file mode 100644 index 00000000000..8dd04840e20 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ContainerSelectorActiveEventHandler.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Windows.Forms.Design.ContainerSelectorActiveEventHandler..ctor(System.Object,System.IntPtr)")] + +namespace System.Windows.Forms.Design +{ + /// + /// Represents the method that will handle a ContainerSelectorActive event. + /// + internal delegate void ContainerSelectorActiveEventHandler(object sender, ContainerSelectorActiveEventArgs e); +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ControlDesigner.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ControlDesigner.cs index 1b025ee64be..3c8844f8c2f 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ControlDesigner.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ControlDesigner.cs @@ -83,6 +83,30 @@ public bool AutoResizeHandles /// public virtual SelectionRules SelectionRules => throw new NotImplementedException(SR.NotImplementedByDesign); + // This boolean indicates whether the Control will allow SnapLines to be shown when any other targetControl is dragged on the design surface. + // This is true by default. + internal virtual bool ControlSupportsSnaplines + { + get => true; + } + + /// Used when adding snaplines + /// In order to add padding, we need to get the offset from the usable client area of our control and the actual origin of our control. In other words: how big is the non-client area here? + /// Ex: we want to add padding on a form to the insides of the borders and below the titlebar. + internal Point GetOffsetToClientArea() + { + NativeMethods.POINT nativeOffset = new NativeMethods.POINT(0, 0); + NativeMethods.MapWindowPoints(Control.Handle, Control.Parent.Handle, nativeOffset, 1); + + Point offset = Control.Location; + // If the 2 controls do not have the same orientation, then force one to make sure we calculate the correct offset + if (Control.IsMirrored != Control.Parent.IsMirrored) + { + offset.Offset(Control.Width, 0); + } + return (new Point(Math.Abs(nativeOffset.x - offset.X), nativeOffset.y - offset.Y)); + } + /// /// Returns a list of SnapLine objects representing interesting /// alignment points for this control. These SnapLines are used @@ -204,6 +228,11 @@ protected virtual ControlBodyGlyph GetControlGlyph(GlyphSelectionType selectionT throw new NotImplementedException(SR.NotImplementedByDesign); } + internal ControlBodyGlyph GetControlGlyphInternal(GlyphSelectionType selectionType) + { + return GetControlGlyph(selectionType); + } + /// /// Returns a collection of Glyph objects representing the selection /// borders and grab handles for a standard control. Note that diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionHeaderItem.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionHeaderItem.cs new file mode 100644 index 00000000000..ed44f3cbedf --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionHeaderItem.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + internal sealed class DesignerActionHeaderItem : DesignerActionTextItem + { + public DesignerActionHeaderItem(string displayName) : base(displayName, displayName) + { + } + public DesignerActionHeaderItem(string displayName, string category) : base(displayName, category) + { + } + + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventArgs.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventArgs.cs new file mode 100644 index 00000000000..734aea70427 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventArgs.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + /// + /// This EventArgs class is used by the DesignerActionService to signify that there has been a change in DesignerActionLists (added or removed) on the related object. + /// + public class DesignerActionListsChangedEventArgs : EventArgs + { + + private readonly object _relatedObject; + private readonly DesignerActionListCollection _actionLists; + private readonly DesignerActionListsChangedType _changeType; + + /// + /// Constructor that requires the object in question, the type of change and the remaining actionlists left for the object. on the related object. + /// + public DesignerActionListsChangedEventArgs(object relatedObject, DesignerActionListsChangedType changeType, DesignerActionListCollection actionLists) + { + _relatedObject = relatedObject; + _changeType = changeType; + _actionLists = actionLists; + } + + /// + /// The type of changed that caused the related event to be thrown. + /// + public DesignerActionListsChangedType ChangeType => _changeType; + + /// + /// The object this change is related to. + /// + public object RelatedObject => _relatedObject; + + /// + /// The remaining actionlists left for the related object. + /// + public DesignerActionListCollection ActionLists => _actionLists; + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventHandler.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventHandler.cs new file mode 100644 index 00000000000..700ebd2583e --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedEventHandler.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + /// + /// This event is thown by the DesignerActionListservice when a shortcut is either added or removed to/from the related object. + /// + [System.Runtime.InteropServices.ComVisible(true)] + public delegate void DesignerActionListsChangedEventHandler(object sender, DesignerActionListsChangedEventArgs e); +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedType.cs new file mode 100644 index 00000000000..b901681903b --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionListsChangedType.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + /// + /// An enum that defines what time of action happend to the related object's DesignerActionLists collection. + /// + [System.Runtime.InteropServices.ComVisible(true)] + public enum DesignerActionListsChangedType + { + /// + /// Signifies that one or more DesignerActionList was added. + /// + ActionListsAdded, + + /// + /// Signifies that one or more DesignerActionList was removed. + /// + ActionListsRemoved + + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionPanel.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionPanel.cs new file mode 100644 index 00000000000..2fc3992b6ba --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionPanel.cs @@ -0,0 +1,3171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Drawing.Design; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Windows.Forms; +using System.Windows.Forms.Design; +using System.Windows.Forms.VisualStyles; + +namespace System.ComponentModel.Design +{ + internal sealed class DesignerActionPanel : ContainerControl + { + + public const string ExternDllGdi32 = "gdi32.dll"; + public const string ExternDllUser32 = "user32.dll"; + + private static readonly object s_eventFormActivated = new object(); + private static readonly object s_eventFormDeactivate = new object(); + + private const int EditInputWidth = 150; // The static size of edit controls + private const int ListBoxMaximumHeight = 200; // The maximum height of a dropdown listbox + private const int MinimumWidth = 150; // The minimum overall width of the panel + private const int BottomPadding = 2; // Padding at the bottom of the panel + private const int TopPadding = 2; // Padding at the top of the panel + + private const int LineLeftMargin = 5; // Left padding for all lines + private const int LineRightMargin = 4; // Right padding for all lines + + private const int LineVerticalPadding = 7; // Vertical padding between lines + + private const int TextBoxTopPadding = 4; // Additional padding for top of textbox lines + private const int SeparatorHorizontalPadding = 3; // Left and right padding for separator lines + + private const int TextBoxLineCenterMargin = 5; // Padding between the label region and editor region of a textbox line + private const int TextBoxLineInnerPadding = 1; // Padding within the editor region of a textbox line + + private const int EditorLineSwatchPadding = 1; // Padding for the swatch of an editor line + private const int EditorLineButtonPadding = 1; // Padding for the button of an editor line + + private const int PanelHeaderVerticalPadding = 3; // Vertical padding within the header of the panel + private const int PanelHeaderHorizontalPadding = 5; // Horizontal padding within the header of the panel + + private const int TextBoxHeightFixup = 2; + + private CommandID[] _filteredCommandIDs; + private readonly ToolTip _toolTip; + private readonly List _lines; + private readonly List _lineYPositions; + private readonly List _lineHeights; + + private readonly Color _gradientLightColor = SystemColors.Control; + private readonly Color _gradientDarkColor = SystemColors.Control; + private readonly Color _titleBarColor = SystemColors.ActiveCaption; + private readonly Color _titleBarUnselectedColor = SystemColors.InactiveCaption; + private readonly Color _titleBarTextColor = SystemColors.ActiveCaptionText; + private readonly Color _separatorColor = SystemColors.ControlDark; + private readonly Color _borderColor = SystemColors.ActiveBorder; + private readonly Color _linkColor = SystemColors.HotTrack; + private readonly Color _activeLinkColor = SystemColors.HotTrack; + private readonly Color _labelForeColor = SystemColors.ControlText; + + private readonly IServiceProvider _serviceProvider; + + private bool _inMethodInvoke; + private bool _updatingTasks; + private bool _dropDownActive; + + public DesignerActionPanel(IServiceProvider serviceProvider) + { + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.Opaque, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.UserPaint, true); + + _serviceProvider = serviceProvider; + _lines = new List(); + _lineHeights = new List(); + _lineYPositions = new List(); + _toolTip = new ToolTip(); + + // Try to get the font from the IUIService, otherwise, use the default + IUIService uiService = (IUIService)ServiceProvider.GetService(typeof(IUIService)); + if (uiService != null) + { + Font = (Font)uiService.Styles["DialogFont"]; + + if (uiService.Styles["VsColorPanelGradientDark"] is Color) + { + _gradientDarkColor = (Color)uiService.Styles["VsColorPanelGradientDark"]; + } + if (uiService.Styles["VsColorPanelGradientLight"] is Color) + { + _gradientLightColor = (Color)uiService.Styles["VsColorPanelGradientLight"]; + } + if (uiService.Styles["VsColorPanelHyperLink"] is Color) + { + _linkColor = (Color)uiService.Styles["VsColorPanelHyperLink"]; + } + if (uiService.Styles["VsColorPanelHyperLinkPressed"] is Color) + { + _activeLinkColor = (Color)uiService.Styles["VsColorPanelHyperLinkPressed"]; + } + if (uiService.Styles["VsColorPanelTitleBar"] is Color) + { + _titleBarColor = (Color)uiService.Styles["VsColorPanelTitleBar"]; + } + if (uiService.Styles["VsColorPanelTitleBarUnselected"] is Color) + { + _titleBarUnselectedColor = (Color)uiService.Styles["VsColorPanelTitleBarUnselected"]; + } + if (uiService.Styles["VsColorPanelTitleBarText"] is Color) + { + _titleBarTextColor = (Color)uiService.Styles["VsColorPanelTitleBarText"]; + } + if (uiService.Styles["VsColorPanelBorder"] is Color) + { + _borderColor = (Color)uiService.Styles["VsColorPanelBorder"]; + } + if (uiService.Styles["VsColorPanelSeparator"] is Color) + { + _separatorColor = (Color)uiService.Styles["VsColorPanelSeparator"]; + } + if (uiService.Styles["VsColorPanelText"] is Color) + { + _labelForeColor = (Color)uiService.Styles["VsColorPanelText"]; + } + } + MinimumSize = new Size(150, 0); + } + + public Color ActiveLinkColor + { + get => _activeLinkColor; + } + + public Color BorderColor + { + get => _borderColor; + } + + private bool DropDownActive + { + get => _dropDownActive; + } + + /// + /// Returns the list of commands that should be filtered by the form that hosts this panel. This is done so that these specific commands will not get passed on to VS, and can instead be handled by the panel itself. + /// + public CommandID[] FilteredCommandIDs + { + get + { + if (_filteredCommandIDs == null) + { + _filteredCommandIDs = new CommandID[] { + StandardCommands.Copy, + StandardCommands.Cut, + StandardCommands.Delete, + StandardCommands.F1Help, + StandardCommands.Paste, + StandardCommands.Redo, + StandardCommands.SelectAll, + StandardCommands.Undo, + MenuCommands.KeyCancel, + MenuCommands.KeyReverseCancel, + MenuCommands.KeyDefaultAction, + MenuCommands.KeyEnd, + MenuCommands.KeyHome, + MenuCommands.KeyMoveDown, + MenuCommands.KeyMoveLeft, + MenuCommands.KeyMoveRight, + MenuCommands.KeyMoveUp, + MenuCommands.KeyNudgeDown, + MenuCommands.KeyNudgeHeightDecrease, + MenuCommands.KeyNudgeHeightIncrease, + MenuCommands.KeyNudgeLeft, + MenuCommands.KeyNudgeRight, + MenuCommands.KeyNudgeUp, + MenuCommands.KeyNudgeWidthDecrease, + MenuCommands.KeyNudgeWidthIncrease, + MenuCommands.KeySizeHeightDecrease, + MenuCommands.KeySizeHeightIncrease, + MenuCommands.KeySizeWidthDecrease, + MenuCommands.KeySizeWidthIncrease, + MenuCommands.KeySelectNext, + MenuCommands.KeySelectPrevious, + MenuCommands.KeyShiftEnd, + MenuCommands.KeyShiftHome, + }; + } + return _filteredCommandIDs; + } + } + + /// + /// Gets the Line that currently has input focus. + /// + private Line FocusedLine + { + get + { + Control activeControl = ActiveControl; + if (activeControl != null) + { + return activeControl.Tag as Line; + } + return null; + } + } + + public Color GradientDarkColor + { + get => _gradientDarkColor; + } + + public Color GradientLightColor + { + get => _gradientLightColor; + } + + public bool InMethodInvoke + { + get => _inMethodInvoke; + internal set + { + _inMethodInvoke = value; + } + } + + public Color LinkColor + { + get => _linkColor; + } + + public Color SeparatorColor + { + get => _separatorColor; + } + + private IServiceProvider ServiceProvider + { + get => _serviceProvider; + } + + public Color TitleBarColor + { + get => _titleBarColor; + } + + public Color TitleBarTextColor + { + get => _titleBarTextColor; + } + + public Color TitleBarUnselectedColor + { + get => _titleBarUnselectedColor; + } + + public Color LabelForeColor + { + get => _labelForeColor; + } + + /// + /// Helper event so that Lines can be notified of this event. + /// + private event EventHandler FormActivated + { + add + { + Events.AddHandler(s_eventFormActivated, value); + } + remove + { + Events.RemoveHandler(s_eventFormActivated, value); + } + } + + /// + /// Helper event so that Lines can be notified of this event. + /// + private event EventHandler FormDeactivate + { + add + { + Events.AddHandler(s_eventFormDeactivate, value); + } + remove + { + Events.RemoveHandler(s_eventFormDeactivate, value); + } + } + + private void AddToCategories(LineInfo lineInfo, ListDictionary categories) + { + string categoryName = lineInfo.Item.Category; + if (categoryName == null) + { + categoryName = string.Empty; + } + + ListDictionary category = (ListDictionary)categories[categoryName]; + if (category == null) + { + category = new ListDictionary(); + categories.Add(categoryName, category); + } + + List categoryList = (List)category[lineInfo.List]; + if (categoryList == null) + { + categoryList = new List(); + category.Add(lineInfo.List, categoryList); + } + categoryList.Add(lineInfo); + } + + /// + /// Computes the best possible location (in desktop coordinates) to display the panel, given the size of the panel and the position of its anchor + /// + public static Point ComputePreferredDesktopLocation(Rectangle rectangleAnchor, Size sizePanel, out DockStyle edgeToDock) + { + Rectangle rectScreen = Screen.FromPoint(rectangleAnchor.Location).WorkingArea; + + // Determine where we can draw the panel to minimize clipping. Start with the most preferred position, i.e. bottom-right of anchor + // For the purposes of computing the flags below, assume the anchor to be small enough to ignore its size. + bool fRightOfAnchor = true; + bool fAlignToScreenLeft = false; + + // if the panel is too wide, try flipping to left or aligning to screen left + if (rectangleAnchor.Right + sizePanel.Width > rectScreen.Right) + { // no room at right try at left of anchor + fRightOfAnchor = false; + if (rectangleAnchor.Left - sizePanel.Width < rectScreen.Left) + { // no room at left, either + fAlignToScreenLeft = true; + } + } + + bool fBelowAnchor = (fRightOfAnchor ? true : false); + bool fAlignToScreenTop = false; + + if (fBelowAnchor) + { + // if the panel is too tall, try flipping to top or aligning to screen top + if (rectangleAnchor.Bottom + sizePanel.Height > rectScreen.Bottom) + { // no room at bottom try at top of anchor + fBelowAnchor = false; + if (rectangleAnchor.Top - sizePanel.Height < rectScreen.Top) + { // no room at top, either + fAlignToScreenTop = true; + } + } + } + else + { + // if the panel is too tall, try flipping to bottom or aligning to screen top + if (rectangleAnchor.Top - sizePanel.Height < rectScreen.Top) + { // no room at top try at bottom of anchor + fBelowAnchor = true; + if (rectangleAnchor.Bottom + sizePanel.Height > rectScreen.Bottom) + { // no room at bottom, either + fAlignToScreenTop = true; + } + } + } + + // The flags give us a total of nine possible positions - + // {LeftOfAnchor, RightOfAnchor, AlignToScreenLeft} X {AboveAnchor, BelowAnchor, AlignToScreenTop} + // Out of these, we rule out one combination (AlignToScreenLeft, AlignToScreenTop) because this does not guarantee the alignment of an anchor edge with that of the panel edge + if (fAlignToScreenTop) + { + fAlignToScreenLeft = false; + } + + int x = 0, y = 0; + const int EDGE_SPACE = 0; + edgeToDock = DockStyle.None; + + // Compute the actual position now, based on the flags above, and taking the anchor size into account. + if (fAlignToScreenLeft && fBelowAnchor) + { + x = rectScreen.Left; + y = rectangleAnchor.Bottom + EDGE_SPACE; + edgeToDock = DockStyle.Bottom; + } + else if (fAlignToScreenLeft && !fBelowAnchor) + { + x = rectScreen.Left; + y = rectangleAnchor.Top - sizePanel.Height - EDGE_SPACE; + edgeToDock = DockStyle.Top; + } + else if (fRightOfAnchor && fAlignToScreenTop) + { + x = rectangleAnchor.Right + EDGE_SPACE; + y = rectScreen.Top; + edgeToDock = DockStyle.Right; + } + else if (fRightOfAnchor && fBelowAnchor) + { + x = rectangleAnchor.Right + EDGE_SPACE; + y = rectangleAnchor.Top; + edgeToDock = DockStyle.Right; + } + else if (fRightOfAnchor && !fBelowAnchor) + { + x = rectangleAnchor.Right + EDGE_SPACE; + y = rectangleAnchor.Bottom - sizePanel.Height; + edgeToDock = DockStyle.Right; + } + else if (!fRightOfAnchor && fAlignToScreenTop) + { + x = rectangleAnchor.Left - sizePanel.Width - EDGE_SPACE; + y = rectScreen.Top; + edgeToDock = DockStyle.Left; + } + else if (!fRightOfAnchor && fBelowAnchor) + { + x = rectangleAnchor.Left - sizePanel.Width - EDGE_SPACE; + y = rectangleAnchor.Top; + edgeToDock = DockStyle.Left; + } + else if (!fRightOfAnchor && !fBelowAnchor) + { + x = rectangleAnchor.Right - sizePanel.Width; + y = rectangleAnchor.Top - sizePanel.Height - EDGE_SPACE; + edgeToDock = DockStyle.Top; + } + else + { + Debug.Assert(false); // should never get here + } + return new Point(x, y); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _toolTip.Dispose(); + } + base.Dispose(disposing); + } + + private Size DoLayout(Size proposedSize, bool measureOnly) + { + // REVIEW: Is this a WinForms bug? This shouldn't be called if we're disposing since no one should care about layout + if (Disposing || IsDisposed) + { + return Size.Empty; + } + + int panelWidth = MinimumWidth; + int yPos = 0; + + SuspendLayout(); + try + { + // Clear cached calculated information + _lineYPositions.Clear(); + _lineHeights.Clear(); + + // Layout each line + for (int i = 0; i < _lines.Count; i++) + { + Line line = _lines[i]; + _lineYPositions.Add(yPos); + Size size = line.LayoutControls(yPos, proposedSize.Width, measureOnly); + panelWidth = Math.Max(panelWidth, size.Width); + _lineHeights.Add(size.Height); + yPos += size.Height; + } + } + finally + { + ResumeLayout(!measureOnly); + } + + return new Size(panelWidth, yPos + BottomPadding); + } + + public override Size GetPreferredSize(Size proposedSize) + { + // REVIEW: WinForms calls this inside of PerformLayout() only in DEBUG code. From the comment it looks like it's calling it to verify their own cached preferred size, so we just ignore this call. + if (proposedSize.IsEmpty) + { + return proposedSize; + } + + return DoLayout(proposedSize, true); + } + + private static bool IsReadOnlyProperty(PropertyDescriptor pd) + { + if (pd.IsReadOnly) + { + return true; + } + return (pd.ComponentType.GetProperty(pd.Name).GetSetMethod() == null); + } + + protected override void OnFontChanged(EventArgs e) + { + base.OnFontChanged(e); + UpdateEditXPos(); + // REVIEW: How do we notify Lines that the font has changed? + } + + private void OnFormActivated(object sender, EventArgs e) + { + ((EventHandler)Events[s_eventFormActivated])?.Invoke(sender, e); + } + + private void OnFormClosing(object sender, CancelEventArgs e) + { + if (!e.Cancel && TopLevelControl != null) + { + Debug.Assert(TopLevelControl is Form, "DesignerActionPanel must be hosted on a Form."); + Form form = (Form)TopLevelControl; + if (form != null) + { + form.Activated -= new EventHandler(OnFormActivated); + form.Deactivate -= new EventHandler(OnFormDeactivate); + form.Closing -= new CancelEventHandler(OnFormClosing); + } + } + } + + private void OnFormDeactivate(object sender, EventArgs e) + { + ((EventHandler)Events[s_eventFormDeactivate])?.Invoke(sender, e); + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + if (TopLevelControl is Form form) + { + form.Activated += new EventHandler(OnFormActivated); + form.Deactivate += new EventHandler(OnFormDeactivate); + form.Closing += new CancelEventHandler(OnFormClosing); + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + if (_updatingTasks) + { + return; + } + DoLayout(Size, false); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (_updatingTasks) + { + return; + } + + Rectangle rect = Bounds; + if (RightToLeft == RightToLeft.Yes) + { + using (LinearGradientBrush gradientBrush = new LinearGradientBrush(rect, GradientDarkColor, GradientLightColor, LinearGradientMode.Horizontal)) + { + e.Graphics.FillRectangle(gradientBrush, ClientRectangle); + } + } + else + { + using (LinearGradientBrush gradientBrush = new LinearGradientBrush(rect, GradientLightColor, GradientDarkColor, LinearGradientMode.Horizontal)) + { + e.Graphics.FillRectangle(gradientBrush, ClientRectangle); + } + } + + using (Pen borderPen = new Pen(BorderColor)) + { + e.Graphics.DrawRectangle(borderPen, new Rectangle(0, 0, Width - 1, Height - 1)); + } + + Rectangle originalClip = e.ClipRectangle; + + // Determine the first line index to paint + int index = 0; + while ((index < (_lineYPositions.Count - 1)) && (_lineYPositions[index + 1] <= originalClip.Top)) + { + index++; + } + + Graphics g = e.Graphics; + for (int i = index; i < _lineYPositions.Count; i++) + { + Line line = _lines[i]; + + int yPos = _lineYPositions[i]; + int lineHeight = _lineHeights[i]; + int lineWidth = Width; + + // Set the clip rectangle so the lines can't mess with each other + g.SetClip(new Rectangle(0, yPos, lineWidth, lineHeight)); + + // Normalize the paint coordinates + g.TranslateTransform(0, yPos); + line.PaintLine(g, lineWidth, lineHeight); + g.ResetTransform(); + + // Stop if we've painted all the lines in the clip rectangle + if (yPos + lineHeight > originalClip.Bottom) + { + break; + } + } + } + + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + + PerformLayout(); + } + + protected override bool ProcessDialogKey(Keys keyData) + { + Line focusedLine = FocusedLine; + if (focusedLine != null) + { + if (focusedLine.ProcessDialogKey(keyData)) + { + return true; + } + } + + return base.ProcessDialogKey(keyData); + } + + // we want to loop + protected override bool ProcessTabKey(bool forward) + { + return (SelectNextControl(ActiveControl, forward, true, true, true)); + } + + private void ProcessLists(DesignerActionListCollection lists, ListDictionary categories) + { + if (lists == null) + { + return; + } + foreach (DesignerActionList list in lists) + { + if (list != null) + { + IEnumerable items = list.GetSortedActionItems(); + if (items != null) + { + foreach (DesignerActionItem item in items) + { + if (item == null) + { + continue; + } + + LineInfo lineInfo = ProcessTaskItem(list, item); + if (lineInfo == null) + { + continue; + } + + AddToCategories(lineInfo, categories); + + // Process lists from related component + IComponent relatedComponent = null; + if (item is DesignerActionPropertyItem propItem) + { + relatedComponent = propItem.RelatedComponent; + } + else + { + if (item is DesignerActionMethodItem methodItem) + { + relatedComponent = methodItem.RelatedComponent; + } + } + + if (relatedComponent != null) + { + IEnumerable relatedLineInfos = ProcessRelatedTaskItems(relatedComponent); + if (relatedLineInfos != null) + { + foreach (LineInfo relatedLineInfo in relatedLineInfos) + { + AddToCategories(relatedLineInfo, categories); + } + } + } + } + } + } + } + } + + private IEnumerable ProcessRelatedTaskItems(IComponent relatedComponent) + { + // Add the related tasks + Debug.Assert(relatedComponent != null); + + DesignerActionListCollection relatedLists = null; + + DesignerActionService actionService = (DesignerActionService)ServiceProvider.GetService(typeof(DesignerActionService)); + if (actionService != null) + { + relatedLists = actionService.GetComponentActions(relatedComponent); + } + else + { + // Try to use the component's service provider if it exists so that we end up getting the right IDesignerHost. + IServiceProvider serviceProvider = relatedComponent.Site; + if (serviceProvider == null) + { + serviceProvider = ServiceProvider; + } + IDesignerHost host = (IDesignerHost)serviceProvider.GetService(typeof(IDesignerHost)); + if (host != null) + { + if (host.GetDesigner(relatedComponent) is ComponentDesigner componentDesigner) + { + relatedLists = componentDesigner.ActionLists; + } + } + } + + List lineInfos = new List(); + if (relatedLists != null) + { + foreach (DesignerActionList relatedList in relatedLists) + { + if (relatedList != null) + { + IEnumerable items = relatedList.GetSortedActionItems(); + if (items != null) + { + foreach (DesignerActionItem relatedItem in items) + { + if (relatedItem != null) + { + if (relatedItem.AllowAssociate) + { + LineInfo lineInfo = ProcessTaskItem(relatedList, relatedItem); + if (lineInfo != null) + { + lineInfos.Add(lineInfo); + } + } + } + } + } + } + } + } + return lineInfos; + } + + private LineInfo ProcessTaskItem(DesignerActionList list, DesignerActionItem item) + { + Line newLine; + if (item is DesignerActionMethodItem) + { + newLine = new MethodLine(_serviceProvider, this); + } + else if (item is DesignerActionPropertyItem pti) + { + PropertyDescriptor pd = TypeDescriptor.GetProperties(list)[pti.MemberName]; + if (pd == null) + { + throw new InvalidOperationException(string.Format(SR.DesignerActionPanel_CouldNotFindProperty, pti.MemberName, list.GetType().FullName)); + } + + TypeDescriptorContext context = new TypeDescriptorContext(_serviceProvider, pd, list); + + UITypeEditor editor = (UITypeEditor)pd.GetEditor(typeof(UITypeEditor)); + bool standardValuesSupported = pd.Converter.GetStandardValuesSupported(context); + + if (editor == null) + { + if (pd.PropertyType == typeof(bool)) + { + if (IsReadOnlyProperty(pd)) + { + newLine = new TextBoxPropertyLine(_serviceProvider, this); + } + else + { + newLine = new CheckBoxPropertyLine(_serviceProvider, this); + } + } + else if (standardValuesSupported) + { + newLine = new EditorPropertyLine(_serviceProvider, this); + } + else + { + newLine = new TextBoxPropertyLine(_serviceProvider, this); + } + } + else + { + newLine = new EditorPropertyLine(_serviceProvider, this); + } + } + else if (item is DesignerActionTextItem) + { + if (item is DesignerActionHeaderItem) + { + newLine = new HeaderLine(_serviceProvider, this); + } + else + { + newLine = new TextLine(_serviceProvider, this); + } + } + else + { + // Ignore unknown items + return null; + } + + return new LineInfo(list, item, newLine); + } + + private void SetDropDownActive(bool active) + { + _dropDownActive = active; + } + + private void ShowError(string errorMessage) + { + IUIService uiService = (IUIService)ServiceProvider.GetService(typeof(IUIService)); + if (uiService != null) + { + uiService.ShowError(errorMessage); + } + else + { + MessageBoxOptions options = 0; + if (SR.RTL != "RTL_False") + { + options = (MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading); + } + MessageBox.Show(this, errorMessage, string.Format(SR.UIServiceHelper_ErrorCaption), MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, options); + } + } + + /// + /// Strips out ampersands used for mnemonics so that they don't show up + /// in the rendering. + /// - Convert "&&" to "&" + /// - Convert "&x" to "x" + /// - An ampersand by itself at the end of a string is displayed as-is + /// + private static string StripAmpersands(string s) + { + if (string.IsNullOrEmpty(s)) + { + return string.Empty; + } + StringBuilder result = new StringBuilder(s.Length); + for (int i = 0; i < s.Length; i++) + { + if (s[i] == '&') + { + // Skip over the ampersand + i++; + if (i == s.Length) + { + // If we're at the last character just add the ampersand and stop + result.Append('&'); + break; + } + } + result.Append(s[i]); + } + return result.ToString(); + } + + private void UpdateEditXPos() + { + // Find the correct edit control position + int editXPos = 0; + for (int i = 0; i < _lines.Count; i++) + { + if (_lines[i] is TextBoxPropertyLine line) + { + editXPos = Math.Max(editXPos, ((TextBoxPropertyLine)line).GetEditRegionXPos()); + } + } + + // Make all the edit controls line up + for (int i = 0; i < _lines.Count; i++) + { + if (_lines[i] is TextBoxPropertyLine line) + { + line.SetEditRegionXPos(editXPos); + } + } + } + + public void UpdateTasks(DesignerActionListCollection actionLists, DesignerActionListCollection serviceActionLists, string title, string subtitle) + { + _updatingTasks = true; + + SuspendLayout(); + try + { + AccessibleName = title; + AccessibleDescription = subtitle; + + // Store the focus state + string focusId = string.Empty; + Line focusedLine = FocusedLine; + if (focusedLine != null) + { + focusId = focusedLine.FocusId; + } + + // Merge the categories from the lists and create controls for each of the items + ListDictionary categories = new ListDictionary(); + + ProcessLists(actionLists, categories); + ProcessLists(serviceActionLists, categories); + + + // Create a flat list of lines w/ separators + List newLines = new List + { + + // Always add a special line for the header + new LineInfo(null, new DesignerActionPanelHeaderItem(title, subtitle), new PanelHeaderLine(_serviceProvider, this)) + }; + + int categoriesIndex = 0; + foreach (ListDictionary category in categories.Values) + { + int categoryIndex = 0; + foreach (List categoryList in category.Values) + { + for (int i = 0; i < categoryList.Count; i++) + { + newLines.Add(categoryList[i]); + } + + categoryIndex++; + + // Add a sub-separator + if (categoryIndex < category.Count) + { + newLines.Add(new LineInfo(null, null, new SeparatorLine(_serviceProvider, this, true))); + } + } + + categoriesIndex++; + // Add a separator + if (categoriesIndex < categories.Count) + { + newLines.Add(new LineInfo(null, null, new SeparatorLine(_serviceProvider, this))); + } + } + + // Now try to update similar lines + int currentTabIndex = 0; + for (int i = 0; i < newLines.Count; i++) + { + LineInfo newLineInfo = newLines[i]; + Line newLine = newLineInfo.Line; + + // See if we can update an old line + bool updated = false; + if (i < _lines.Count) + { + Line oldLine = _lines[i]; + + if (oldLine.GetType() == newLine.GetType()) + { + oldLine.UpdateActionItem(newLineInfo.List, newLineInfo.Item, _toolTip, ref currentTabIndex); + updated = true; + } + else + { + oldLine.RemoveControls(Controls); + _lines.RemoveAt(i); + } + } + + if (!updated) + { + // Add the new controls + List newControlList = newLine.GetControls(); + Control[] controls = new Control[newControlList.Count]; + newControlList.CopyTo(controls); + Controls.AddRange(controls); + + newLine.UpdateActionItem(newLineInfo.List, newLineInfo.Item, _toolTip, ref currentTabIndex); + _lines.Insert(i, newLine); + } + } + + // Remove any excess lines + for (int i = _lines.Count - 1; i >= newLines.Count; i--) + { + Line excessLine = _lines[i]; + excessLine.RemoveControls(Controls); + _lines.RemoveAt(i); + } + + // Restore focus + if (!string.IsNullOrEmpty(focusId)) + { + foreach (Line line in _lines) + { + if (string.Equals(line.FocusId, focusId, StringComparison.Ordinal)) + { + line.Focus(); + } + } + } + } + finally + { + UpdateEditXPos(); + _updatingTasks = false; + // REVIEW: We should rely on the caller to actually perform layout since it our scenarios, the entire right pane will have to be layed out + // Actually, we do want to resume layout since invalidation causes an OnPaint, and OnPaint relies on everything being layed out already + ResumeLayout(true); + } + Invalidate(); + } + + private class LineInfo + { + public Line Line; + public DesignerActionItem Item; + public DesignerActionList List; + public LineInfo(DesignerActionList list, DesignerActionItem item, Line line) + { + Debug.Assert(line != null); + Line = line; + Item = item; + List = list; + } + } + + internal sealed class TypeDescriptorContext : ITypeDescriptorContext + { + private readonly IServiceProvider _serviceProvider; + private readonly PropertyDescriptor _propDesc; + private readonly object _instance; + + public TypeDescriptorContext(IServiceProvider serviceProvider, PropertyDescriptor propDesc, object instance) + { + _serviceProvider = serviceProvider; + _propDesc = propDesc; + _instance = instance; + } + + private IComponentChangeService ComponentChangeService + { + get => (IComponentChangeService)_serviceProvider.GetService(typeof(IComponentChangeService)); + } + + public IContainer Container + { + get => (IContainer)_serviceProvider.GetService(typeof(IContainer)); + } + + public object Instance + { + get => _instance; + } + + public PropertyDescriptor PropertyDescriptor + { + get => _propDesc; + } + + public object GetService(Type serviceType) + { + return _serviceProvider.GetService(serviceType); + } + + public bool OnComponentChanging() + { + if (ComponentChangeService != null) + { + try + { + ComponentChangeService.OnComponentChanging(_instance, _propDesc); + } + catch (CheckoutException ce) + { + if (ce == CheckoutException.Canceled) + { + return false; + } + throw ce; + } + } + return true; + } + + public void OnComponentChanged() + { + if (ComponentChangeService != null) + { + ComponentChangeService.OnComponentChanged(_instance, _propDesc, null, null); + } + } + } + + private abstract class Line + { + private readonly DesignerActionPanel _actionPanel; + private List _addedControls; + private readonly IServiceProvider _serviceProvider; + + public Line(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) + { + _serviceProvider = serviceProvider; + _actionPanel = actionPanel ?? throw new ArgumentNullException(nameof(actionPanel)); + } + + protected DesignerActionPanel ActionPanel + { + get => _actionPanel; + } + + public abstract string FocusId { get; } + + public IServiceProvider ServiceProvider => _serviceProvider; + + protected abstract void AddControls(List controls); + + internal List GetControls() + { + _addedControls = new List(); + AddControls(_addedControls); + // Tag all the controls with the Line so we know who owns it + foreach (Control c in _addedControls) + { + c.Tag = this; + } + return _addedControls; + } + + public abstract void Focus(); + + public abstract Size LayoutControls(int top, int width, bool measureOnly); + + public virtual void PaintLine(Graphics g, int lineWidth, int lineHeight) + { + } + + protected internal virtual bool ProcessDialogKey(Keys keyData) + { + return false; + } + + internal void RemoveControls(Control.ControlCollection controls) + { + for (int i = 0; i < _addedControls.Count; i++) + { + Control c = _addedControls[i]; + c.Tag = null; + controls.Remove(c); + } + } + + internal abstract void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex); + } + + private sealed class DesignerActionPanelHeaderItem : DesignerActionItem + { + private readonly string _subtitle; + + public DesignerActionPanelHeaderItem(string title, string subtitle) : base(title, null, null) + { + _subtitle = subtitle; + } + + public string Subtitle => _subtitle; + } + + private sealed class PanelHeaderLine : Line + { + private DesignerActionList _actionList; + private DesignerActionPanelHeaderItem _panelHeaderItem; + private Label _titleLabel; + private Label _subtitleLabel; + private bool _formActive; + + public PanelHeaderLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) + : base(serviceProvider, actionPanel) + { + actionPanel.FontChanged += new EventHandler(OnParentControlFontChanged); + } + + public sealed override string FocusId + { + get => string.Empty; + } + public DesignerActionList ActionList { get => _actionList; set => _actionList = value; } + + protected override void AddControls(List controls) + { + _titleLabel = new Label + { + BackColor = Color.Transparent, + ForeColor = ActionPanel.TitleBarTextColor, + TextAlign = Drawing.ContentAlignment.MiddleLeft, + UseMnemonic = false + }; + _subtitleLabel = new Label + { + BackColor = Color.Transparent, + ForeColor = ActionPanel.TitleBarTextColor, + TextAlign = Drawing.ContentAlignment.MiddleLeft, + UseMnemonic = false + }; + + controls.Add(_titleLabel); + controls.Add(_subtitleLabel); + + ActionPanel.FormActivated += new EventHandler(OnFormActivated); + ActionPanel.FormDeactivate += new EventHandler(OnFormDeactivate); + } + + public sealed override void Focus() + { + Debug.Fail("Should never try to focus a PanelHeaderLine"); + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + Size titleSize = _titleLabel.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + Size subtitleSize = Size.Empty; + if (!string.IsNullOrEmpty(_panelHeaderItem.Subtitle)) + { + subtitleSize = _subtitleLabel.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + } + + if (!measureOnly) + { + _titleLabel.Location = new Point(LineLeftMargin, top + PanelHeaderVerticalPadding); + _titleLabel.Size = titleSize; + _subtitleLabel.Location = new Point(LineLeftMargin, top + PanelHeaderVerticalPadding * 2 + titleSize.Height); + _subtitleLabel.Size = subtitleSize; + } + + int newWidth = Math.Max(titleSize.Width, subtitleSize.Width) + 2 * PanelHeaderHorizontalPadding; + int newHeight = (subtitleSize.IsEmpty ? (titleSize.Height + 2 * PanelHeaderVerticalPadding) : (titleSize.Height + subtitleSize.Height + 3 * PanelHeaderVerticalPadding)); + return new Size(newWidth + 2, newHeight + 1); + } + + private void OnFormActivated(object sender, EventArgs e) + { + _formActive = true; + ActionPanel.Invalidate(); + } + + private void OnFormDeactivate(object sender, EventArgs e) + { + _formActive = false; + ActionPanel.Invalidate(); + } + + private void OnParentControlFontChanged(object sender, EventArgs e) + { + if (_titleLabel != null && _subtitleLabel != null) + { + _titleLabel.Font = new Font(ActionPanel.Font, FontStyle.Bold); + _subtitleLabel.Font = ActionPanel.Font; + } + } + + public override void PaintLine(Graphics g, int lineWidth, int lineHeight) + { + Color backColor = (_formActive || ActionPanel.DropDownActive) ? _ = ActionPanel.TitleBarColor : ActionPanel.TitleBarUnselectedColor; + + using (SolidBrush b = new SolidBrush(backColor)) + { + g.FillRectangle(b, 1, 1, lineWidth - 2, lineHeight - 1); + } + + // Paint a line under the title label + using (Pen p = new Pen(ActionPanel.BorderColor)) + { + g.DrawLine(p, 0, lineHeight - 1, lineWidth, lineHeight - 1); + } + } + + internal override void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex) + { + ActionList = actionList; + _panelHeaderItem = (DesignerActionPanelHeaderItem)actionItem; + _titleLabel.Text = _panelHeaderItem.DisplayName; + _titleLabel.TabIndex = currentTabIndex++; + _subtitleLabel.Text = _panelHeaderItem.Subtitle; + _subtitleLabel.TabIndex = currentTabIndex++; + _subtitleLabel.Visible = (_subtitleLabel.Text.Length != 0); + + // Force the font to update + OnParentControlFontChanged(null, EventArgs.Empty); + } + } + + private sealed class MethodLine : Line + { + private DesignerActionList _actionList; + private DesignerActionMethodItem _methodItem; + private MethodItemLinkLabel _linkLabel; + + public MethodLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel) + { + } + public sealed override string FocusId + { + get => "METHOD:" + _actionList.GetType().FullName + "." + _methodItem.MemberName; + } + + protected override void AddControls(List controls) + { + _linkLabel = new MethodItemLinkLabel + { + ActiveLinkColor = ActionPanel.ActiveLinkColor, + AutoSize = false, + BackColor = Color.Transparent, + LinkBehavior = LinkBehavior.HoverUnderline, + LinkColor = ActionPanel.LinkColor, + TextAlign = Drawing.ContentAlignment.MiddleLeft, + UseMnemonic = false, + VisitedLinkColor = ActionPanel.LinkColor + }; + _linkLabel.LinkClicked += new LinkLabelLinkClickedEventHandler(OnLinkLabelLinkClicked); + + controls.Add(_linkLabel); + } + + public sealed override void Focus() + { + _linkLabel.Focus(); + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + Size linkLabelSize = _linkLabel.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + + if (!measureOnly) + { + _linkLabel.Location = new Point(LineLeftMargin, top + LineVerticalPadding / 2); + _linkLabel.Size = linkLabelSize; + } + + return linkLabelSize + new Size(LineLeftMargin + LineRightMargin, LineVerticalPadding); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + private void OnLinkLabelLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Debug.Assert(!ActionPanel.InMethodInvoke, "Nested method invocation"); + ActionPanel.InMethodInvoke = true; + try + { + _methodItem.Invoke(); + } + catch (Exception ex) + { + if (ex is TargetInvocationException) + { + ex = ex.InnerException; + } + // NOTE: We had code to rethrow if this was one of [NullReferenceException, StackOverflowException, OutOfMemoryException, ThreadAbortException]. Removing this rethrow. StackOverflow and ThreadAbort can't be meaningfully caught, and NullRef and OutOfMemory really shouldn't be caught. Out of these, OOM is the most correct one to call, but OOM is thrown by GDI+ for pretty much any problem, so isn't reliable as an actual indicator that you're out of memory. If you really are out of memory, it's very likely you'll get another OOM shortly. + ActionPanel.ShowError(string.Format(SR.DesignerActionPanel_ErrorInvokingAction, _methodItem.DisplayName, Environment.NewLine + ex.Message)); + } + finally + { + ActionPanel.InMethodInvoke = false; + } + } + + internal override void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex) + { + _actionList = actionList; + _methodItem = (DesignerActionMethodItem)actionItem; + toolTip.SetToolTip(_linkLabel, _methodItem.Description); + _linkLabel.Text = StripAmpersands(_methodItem.DisplayName); + _linkLabel.AccessibleDescription = actionItem.Description; + _linkLabel.TabIndex = currentTabIndex++; + } + + private sealed class MethodItemLinkLabel : LinkLabel + { + protected override bool ProcessDialogKey(Keys keyData) + { + if ((keyData & Keys.Control) == Keys.Control) + { + Keys keyCode = keyData & Keys.KeyCode; + switch (keyCode) + { + case Keys.Tab: + // We specifically ignore Ctrl+Tab because it prevents the window switcher dialog from showing up in VS. Normally the key combination is only needed when a LinkLabel contains multiple links, but that can't happen inside the DesignerActionPanel. + return false; + } + } + return base.ProcessDialogKey(keyData); + } + } + } + + private abstract class PropertyLine : Line + { + private DesignerActionList _actionList; + private DesignerActionPropertyItem _propertyItem; + private object _value; + private bool _pushingValue; + private PropertyDescriptor _propDesc; + private ITypeDescriptorContext _typeDescriptorContext; + + public PropertyLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel) + { + } + + public sealed override string FocusId + { + get => "PROPERTY:" + _actionList.GetType().FullName + "." + _propertyItem.MemberName; + } + + protected PropertyDescriptor PropertyDescriptor + { + get + { + if (_propDesc == null) + { + _propDesc = TypeDescriptor.GetProperties(_actionList)[_propertyItem.MemberName]; + } + + return _propDesc; + } + } + + protected DesignerActionPropertyItem PropertyItem + { + get => _propertyItem; + } + + protected ITypeDescriptorContext TypeDescriptorContext + { + get + { + if (_typeDescriptorContext == null) + { + _typeDescriptorContext = new TypeDescriptorContext(ServiceProvider, PropertyDescriptor, _actionList); + } + return _typeDescriptorContext; + } + } + + protected object Value { get; } + + protected abstract void OnPropertyTaskItemUpdated(ToolTip toolTip, ref int currentTabIndex); + + protected abstract void OnValueChanged(); + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + protected void SetValue(object newValue) + { + if (_pushingValue || ActionPanel.DropDownActive) + { + return; + } + _pushingValue = true; + try + { + // Only push the change if the values are different + if (newValue != null) + { + Type valueType = newValue.GetType(); + // If it's not assignable, try to convert it + if (!PropertyDescriptor.PropertyType.IsAssignableFrom(valueType)) + { + if (PropertyDescriptor.Converter != null) + { + // If we can't convert it, show an error + if (!PropertyDescriptor.Converter.CanConvertFrom(_typeDescriptorContext, valueType)) + { + ActionPanel.ShowError(string.Format(SR.DesignerActionPanel_CouldNotConvertValue, newValue, _propDesc.PropertyType)); + return; + } + else + { + newValue = PropertyDescriptor.Converter.ConvertFrom(_typeDescriptorContext, CultureInfo.CurrentCulture, newValue); + } + } + } + } + if (!Object.Equals(_value, newValue)) + { + PropertyDescriptor.SetValue(_actionList, newValue); + // Update the value we're caching + _value = PropertyDescriptor.GetValue(_actionList); + + OnValueChanged(); + } + } + catch (Exception e) + { + if (e is TargetInvocationException) + { + e = e.InnerException; + } + ActionPanel.ShowError(string.Format(SR.DesignerActionPanel_ErrorSettingValue, newValue, PropertyDescriptor.Name, e.Message)); + } + finally + { + _pushingValue = false; + } + } + + internal sealed override void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex) + { + _actionList = actionList; + _propertyItem = (DesignerActionPropertyItem)actionItem; + _propDesc = null; + _typeDescriptorContext = null; + _value = PropertyDescriptor.GetValue(actionList); + OnPropertyTaskItemUpdated(toolTip, ref currentTabIndex); + _pushingValue = true; + try + { + OnValueChanged(); + } + finally + { + _pushingValue = false; + } + } + } + + private sealed class CheckBoxPropertyLine : PropertyLine + { + private CheckBox _checkBox; + + public CheckBoxPropertyLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel) + { + } + + protected override void AddControls(List controls) + { + _checkBox = new CheckBox + { + BackColor = Color.Transparent, + CheckAlign = Drawing.ContentAlignment.MiddleLeft + }; + _checkBox.CheckedChanged += new EventHandler(OnCheckBoxCheckedChanged); + _checkBox.TextAlign = Drawing.ContentAlignment.MiddleLeft; + _checkBox.UseMnemonic = false; + _checkBox.ForeColor = ActionPanel.LabelForeColor; + + controls.Add(_checkBox); + } + + public sealed override void Focus() + { + _checkBox.Focus(); + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + Size checkBoxPreferredSize = _checkBox.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + + if (!measureOnly) + { + _checkBox.Location = new Point(LineLeftMargin, top + LineVerticalPadding / 2); + _checkBox.Size = checkBoxPreferredSize; + } + + return checkBoxPreferredSize + new Size(LineLeftMargin + LineRightMargin, LineVerticalPadding); + } + + private void OnCheckBoxCheckedChanged(object sender, EventArgs e) + { + SetValue(_checkBox.Checked); + } + + protected override void OnPropertyTaskItemUpdated(ToolTip toolTip, ref int currentTabIndex) + { + _checkBox.Text = StripAmpersands(PropertyItem.DisplayName); + _checkBox.AccessibleDescription = PropertyItem.Description; + _checkBox.TabIndex = currentTabIndex++; + + toolTip.SetToolTip(_checkBox, PropertyItem.Description); + } + + protected override void OnValueChanged() + { + _checkBox.Checked = (bool)Value; + } + } + + private class TextBoxPropertyLine : PropertyLine + { + private TextBox _textBox; + private EditorLabel _readOnlyTextBoxLabel; + private Control _editControl; + private Label _label; + + private int _editXPos; + private bool _textBoxDirty; + + private Point _editRegionLocation; + private Point _editRegionRelativeLocation; + private Size _editRegionSize; + + public TextBoxPropertyLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel) + { + } + protected Control EditControl + { get; } + + protected Point EditRegionLocation { get; } + + protected Point EditRegionRelativeLocation { get; } + + protected Size EditRegionSize { get; } + + public Point EditRegionRelativeLocation1 { get => _editRegionRelativeLocation; set => _editRegionRelativeLocation = value; } + + protected override void AddControls(List controls) + { + _label = new Label + { + BackColor = Color.Transparent, + ForeColor = ActionPanel.LabelForeColor, + TextAlign = Drawing.ContentAlignment.MiddleLeft, + UseMnemonic = false + }; + + _readOnlyTextBoxLabel = new EditorLabel + { + BackColor = Color.Transparent, + ForeColor = SystemColors.WindowText, + TabStop = true, + TextAlign = Drawing.ContentAlignment.TopLeft, + UseMnemonic = false, + Visible = false + }; + _readOnlyTextBoxLabel.MouseClick += new MouseEventHandler(OnReadOnlyTextBoxLabelClick); + _readOnlyTextBoxLabel.Enter += new EventHandler(OnReadOnlyTextBoxLabelEnter); + _readOnlyTextBoxLabel.Leave += new EventHandler(OnReadOnlyTextBoxLabelLeave); + _readOnlyTextBoxLabel.KeyDown += new KeyEventHandler(OnReadOnlyTextBoxLabelKeyDown); + + _textBox = new TextBox + { + BorderStyle = BorderStyle.None, + TextAlign = System.Windows.Forms.HorizontalAlignment.Left, + Visible = false + }; + _textBox.TextChanged += new EventHandler(OnTextBoxTextChanged); + _textBox.KeyDown += new KeyEventHandler(OnTextBoxKeyDown); + _textBox.LostFocus += new EventHandler(OnTextBoxLostFocus); + + controls.Add(_readOnlyTextBoxLabel); + controls.Add(_textBox); + controls.Add(_label); + } + + public sealed override void Focus() + { + _editControl.Focus(); + } + + internal int GetEditRegionXPos() + { + if (string.IsNullOrEmpty(_label.Text)) + { + return LineLeftMargin; + } + return LineLeftMargin + _label.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)).Width + TextBoxLineCenterMargin; + } + + protected virtual int GetTextBoxLeftPadding(int textBoxHeight) + { + return TextBoxLineInnerPadding; + } + + protected virtual int GetTextBoxRightPadding(int textBoxHeight) + { + return TextBoxLineInnerPadding; + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + // Figure out our minimum width + // Compare to proposed width + // If we are smaller, widen the textbox to fit the line based on the bonus + int textBoxPreferredHeight = _textBox.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)).Height; + textBoxPreferredHeight += TextBoxHeightFixup; + int height = textBoxPreferredHeight + LineVerticalPadding + TextBoxLineInnerPadding * 2 + 2; // 2 == border size + int editRegionXPos = Math.Max(_editXPos, GetEditRegionXPos()); + int minimumWidth = editRegionXPos + EditInputWidth + LineRightMargin; + width = Math.Max(width, minimumWidth); + int textBoxWidthBonus = width - minimumWidth; + + if (!measureOnly) + { + _editRegionLocation = new Point(editRegionXPos, top + TextBoxTopPadding); + EditRegionRelativeLocation1 = new Point(editRegionXPos, TextBoxTopPadding); + _editRegionSize = new Size(EditInputWidth + textBoxWidthBonus, textBoxPreferredHeight + TextBoxLineInnerPadding * 2); + + _label.Location = new Point(LineLeftMargin, top); + int labelPreferredWidth = _label.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)).Width; + _label.Size = new Size(labelPreferredWidth, height); + + int specialPadding = 0; + if (_editControl is TextBox) + { + specialPadding = 2; + } + _editControl.Location = new Point(_editRegionLocation.X + GetTextBoxLeftPadding(textBoxPreferredHeight) + 1 + specialPadding, _editRegionLocation.Y + TextBoxLineInnerPadding + 1); + _editControl.Width = _editRegionSize.Width - GetTextBoxRightPadding(textBoxPreferredHeight) - GetTextBoxLeftPadding(textBoxPreferredHeight) - specialPadding; + _editControl.Height = _editRegionSize.Height - TextBoxLineInnerPadding * 2 - 1; + } + + return new Size(width, height); + } + + protected virtual bool IsReadOnly() + { + return IsReadOnlyProperty(PropertyDescriptor); + } + + protected override void OnPropertyTaskItemUpdated(ToolTip toolTip, ref int currentTabIndex) + { + _label.Text = StripAmpersands(PropertyItem.DisplayName); + _label.TabIndex = currentTabIndex++; + toolTip.SetToolTip(_label, PropertyItem.Description); + _textBoxDirty = false; + + if (IsReadOnly()) + { + _readOnlyTextBoxLabel.Visible = true; + + _textBox.Visible = false; + // REVIEW: Setting Visible to false doesn't seem to work, so position far away + _textBox.Location = new Point(int.MaxValue, int.MaxValue); + + _editControl = _readOnlyTextBoxLabel; + } + else + { + _readOnlyTextBoxLabel.Visible = false; + // REVIEW: Setting Visible to false doesn't seem to work, so position far away + _readOnlyTextBoxLabel.Location = new Point(int.MaxValue, int.MaxValue); + + _textBox.Visible = true; + + _editControl = _textBox; + } + _editControl.AccessibleDescription = PropertyItem.Description; + _editControl.AccessibleName = StripAmpersands(PropertyItem.DisplayName); + _editControl.TabIndex = currentTabIndex++; + + _editControl.BringToFront(); + } + + protected virtual void OnReadOnlyTextBoxLabelClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + Focus(); + } + } + + private void OnReadOnlyTextBoxLabelEnter(object sender, EventArgs e) + { + _readOnlyTextBoxLabel.ForeColor = SystemColors.HighlightText; + _readOnlyTextBoxLabel.BackColor = SystemColors.Highlight; + } + + private void OnReadOnlyTextBoxLabelLeave(object sender, EventArgs e) + { + _readOnlyTextBoxLabel.ForeColor = SystemColors.WindowText; + _readOnlyTextBoxLabel.BackColor = SystemColors.Window; + } + + protected TypeConverter.StandardValuesCollection GetStandardValues() + { + TypeConverter converter = PropertyDescriptor.Converter; + if (converter != null && + converter.GetStandardValuesSupported(TypeDescriptorContext)) + { + return converter.GetStandardValues(TypeDescriptorContext); + } + return null; + } + + private void OnEditControlKeyDown(KeyEventArgs e) + { + if (e.KeyCode == Keys.Down) + { + e.Handled = true; + // Try to find the existing value and then pick the one after it + TypeConverter.StandardValuesCollection standardValues = GetStandardValues(); + if (standardValues != null) + { + for (int i = 0; i < standardValues.Count; i++) + { + if (Object.Equals(Value, standardValues[i])) + { + if (i < standardValues.Count - 1) + { + SetValue(standardValues[i + 1]); + } + return; + } + } + // Previous value wasn't found, select the first one by default + if (standardValues.Count > 0) + { + SetValue(standardValues[0]); + } + } + return; + } + + if (e.KeyCode == Keys.Up) + { + e.Handled = true; + // Try to find the existing value and then pick the one before it + TypeConverter.StandardValuesCollection standardValues = GetStandardValues(); + if (standardValues != null) + { + for (int i = 0; i < standardValues.Count; i++) + { + if (Object.Equals(Value, standardValues[i])) + { + if (i > 0) + { + SetValue(standardValues[i - 1]); + } + return; + } + } + // Previous value wasn't found, select the first one by default + if (standardValues.Count > 0) + { + SetValue(standardValues[standardValues.Count - 1]); + } + } + return; + } + } + + private void OnReadOnlyTextBoxLabelKeyDown(object sender, KeyEventArgs e) + { + // Delegate the rest of the processing to a common helper + OnEditControlKeyDown(e); + } + + private void OnTextBoxKeyDown(object sender, KeyEventArgs e) + { + if (ActionPanel.DropDownActive) + { + return; + } + + if (e.KeyCode == Keys.Enter) + { + UpdateValue(); + e.Handled = true; + return; + } + + // Delegate the rest of the processing to a common helper + OnEditControlKeyDown(e); + } + + private void OnTextBoxLostFocus(object sender, EventArgs e) + { + if (ActionPanel.DropDownActive) + { + return; + } + UpdateValue(); + } + + private void OnTextBoxTextChanged(object sender, EventArgs e) + { + _textBoxDirty = true; + } + + protected override void OnValueChanged() + { + _editControl.Text = PropertyDescriptor.Converter.ConvertToString(TypeDescriptorContext, Value); + } + + public override void PaintLine(Graphics g, int lineWidth, int lineHeight) + { + Rectangle editRect = new Rectangle(EditRegionRelativeLocation, EditRegionSize); + g.FillRectangle(SystemBrushes.Window, editRect); + g.DrawRectangle(SystemPens.ControlDark, editRect); + } + + internal void SetEditRegionXPos(int xPos) + { + // Ignore the x-position if we have no text. This allows the textbox to span the entire width of the panel. + if (!string.IsNullOrEmpty(_label.Text)) + { + _editXPos = xPos; + } + else + { + _editXPos = LineLeftMargin; + } + } + + private void UpdateValue() + { + if (_textBoxDirty) + { + SetValue(_editControl.Text); + _textBoxDirty = false; + } + } + + /// + /// Custom label that provides accurate accessibility information and focus abilities. + /// + private sealed class EditorLabel : Label + { + public EditorLabel() + { + SetStyle(ControlStyles.Selectable, true); + } + + protected override AccessibleObject CreateAccessibilityInstance() + { + return new EditorLabelAccessibleObject(this); + } + + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + // Since we are not a standard focusable control, we have to raise our own accessibility events. + // objectID = OBJID_WINDOW + // childID = CHILDID_SELF - 1 (the -1 is because WinForms always adds 1 to the value) + // (these consts are defined in winuser.h) + AccessibilityNotifyClients(AccessibleEvents.Focus, 0, -1); + } + + protected override bool IsInputKey(Keys keyData) + { + if (keyData == Keys.Down || keyData == Keys.Up) + { + return true; + } + return base.IsInputKey(keyData); + } + + private sealed class EditorLabelAccessibleObject : ControlAccessibleObject + { + public EditorLabelAccessibleObject(EditorLabel owner) : base(owner) + { + } + + public override string Value + { get => Owner.Text; } + } + } + } + + private sealed class EditorPropertyLine : TextBoxPropertyLine, IWindowsFormsEditorService, IServiceProvider + { + private EditorButton _button; + private UITypeEditor _editor; + private bool _hasSwatch; + private Image _swatch; + private FlyoutDialog _dropDownHolder; + private bool _ignoreNextSelectChange = false; + private bool _ignoreDropDownValue; + + public EditorPropertyLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel) + { + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void ActivateDropDown() + { + if (_editor != null) + { + try + { + object newValue = _editor.EditValue(TypeDescriptorContext, this, Value); + SetValue(newValue); + } + catch (Exception ex) + { + ActionPanel.ShowError(string.Format(SR.DesignerActionPanel_ErrorActivatingDropDown, ex.Message)); + } + } + else + { + ListBox listBox = new ListBox + { + BorderStyle = BorderStyle.None, + IntegralHeight = false, + Font = ActionPanel.Font + }; + listBox.SelectedIndexChanged += new EventHandler(OnListBoxSelectedIndexChanged); + listBox.KeyDown += new KeyEventHandler(OnListBoxKeyDown); + + TypeConverter.StandardValuesCollection standardValues = GetStandardValues(); + if (standardValues != null) + { + foreach (object o in standardValues) + { + string newItem = PropertyDescriptor.Converter.ConvertToString(TypeDescriptorContext, CultureInfo.CurrentCulture, o); + listBox.Items.Add(newItem); + + if ((o != null) && o.Equals(Value)) + { + listBox.SelectedItem = newItem; + } + } + } + + // All measurement code borrowed from WinForms PropertyGridView.cs + int maxWidth = 0; + + // The listbox draws with GDI, not GDI+. So, we use a normal DC here. + IntPtr hdc = UnsafeNativeMethods.GetDC(new HandleRef(listBox, listBox.Handle)); + IntPtr hFont = listBox.Font.ToHfont(); + NativeMethods.CommonHandles.s_gdiHandleCollector.Add(); + NativeMethods.TEXTMETRIC tm = new NativeMethods.TEXTMETRIC(); + try + { + hFont = SafeNativeMethods.SelectObject(new HandleRef(listBox, hdc), new HandleRef(listBox.Font, hFont)); + + if (listBox.Items.Count > 0) + { + NativeMethods.SIZE textSize = new NativeMethods.SIZE(); + + foreach (string s in listBox.Items) + { + SafeNativeMethods.GetTextExtentPoint32(new HandleRef(listBox, hdc), s, s.Length, textSize); + maxWidth = Math.Max((int)textSize.cx, maxWidth); + } + } + SafeNativeMethods.GetTextMetrics(new HandleRef(listBox, hdc), ref tm); + + // border + padding + scrollbar + maxWidth += 2 + tm.tmMaxCharWidth + SystemInformation.VerticalScrollBarWidth; + + hFont = SafeNativeMethods.SelectObject(new HandleRef(listBox, hdc), new HandleRef(listBox.Font, hFont)); + } + finally + { + SafeNativeMethods.DeleteObject(new HandleRef(listBox.Font, hFont)); + UnsafeNativeMethods.ReleaseDC(new HandleRef(listBox, listBox.Handle), new HandleRef(listBox, hdc)); + } + + listBox.Height = Math.Max(tm.tmHeight + 2, Math.Min(ListBoxMaximumHeight, listBox.PreferredHeight)); + listBox.Width = Math.Max(maxWidth, EditRegionSize.Width); + + _ignoreDropDownValue = false; + + try + { + ShowDropDown(listBox, SystemColors.ControlDark); + } + finally + { + listBox.SelectedIndexChanged -= new EventHandler(OnListBoxSelectedIndexChanged); + listBox.KeyDown -= new KeyEventHandler(OnListBoxKeyDown); + } + + if (!_ignoreDropDownValue) + { + if (listBox.SelectedItem != null) + { + SetValue(listBox.SelectedItem); + } + } + } + } + + protected override void AddControls(List controls) + { + base.AddControls(controls); + + _button = new EditorButton(); + _button.Click += new EventHandler(OnButtonClick); + _button.GotFocus += new EventHandler(OnButtonGotFocus); + controls.Add(_button); + } + + private void CloseDropDown() + { + if (_dropDownHolder != null) + { + _dropDownHolder.Visible = false; + } + } + + protected override int GetTextBoxLeftPadding(int textBoxHeight) + { + if (_hasSwatch) + { + return base.GetTextBoxLeftPadding(textBoxHeight) + textBoxHeight + 2 * EditorLineSwatchPadding; + } + else + { + return base.GetTextBoxLeftPadding(textBoxHeight); + } + } + + protected override int GetTextBoxRightPadding(int textBoxHeight) + { + return base.GetTextBoxRightPadding(textBoxHeight) + textBoxHeight + 2 * EditorLineButtonPadding; + } + + protected override bool IsReadOnly() + { + if (base.IsReadOnly()) + { + return true; + } + + // If we can't convert from string, we are readonly because we can't convert the user's input + bool converterReadOnly = !PropertyDescriptor.Converter.CanConvertFrom(TypeDescriptorContext, typeof(string)); + + // If standard values are supported and are exclusive, we are readonly + bool standardValuesExclusive = + PropertyDescriptor.Converter.GetStandardValuesSupported(TypeDescriptorContext) && + PropertyDescriptor.Converter.GetStandardValuesExclusive(TypeDescriptorContext); + + return converterReadOnly || standardValuesExclusive; + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + Size size = base.LayoutControls(top, width, measureOnly); + + if (!measureOnly) + { + int buttonHeight = EditRegionSize.Height - EditorLineButtonPadding * 2 - 1; + _button.Location = new Point(EditRegionLocation.X + EditRegionSize.Width - buttonHeight - EditorLineButtonPadding, EditRegionLocation.Y + EditorLineButtonPadding + 1); + _button.Size = new Size(buttonHeight, buttonHeight); + } + return size; + } + + private void OnButtonClick(object sender, EventArgs e) + { + ActivateDropDown(); + } + + private void OnButtonGotFocus(object sender, EventArgs e) + { + if (!_button.Ellipsis) + { + Focus(); + } + } + + private void OnListBoxKeyDown(object sender, KeyEventArgs e) + { + // Always respect the enter key and F4 + if (e.KeyData == Keys.Enter) + { + _ignoreNextSelectChange = false; + CloseDropDown(); + e.Handled = true; + } + else + { + // Ignore selected index change events when the user is navigating via the keyboard + _ignoreNextSelectChange = true; + } + } + + private void OnListBoxSelectedIndexChanged(object sender, EventArgs e) + { + // If we're ignoring this selected index change, do nothing + if (_ignoreNextSelectChange) + { + _ignoreNextSelectChange = false; + } + else + { + CloseDropDown(); + } + } + + protected override void OnPropertyTaskItemUpdated(ToolTip toolTip, ref int currentTabIndex) + { + _editor = (UITypeEditor)PropertyDescriptor.GetEditor(typeof(UITypeEditor)); + + base.OnPropertyTaskItemUpdated(toolTip, ref currentTabIndex); + + if (_editor != null) + { + _button.Ellipsis = (_editor.GetEditStyle(TypeDescriptorContext) == UITypeEditorEditStyle.Modal); + _hasSwatch = _editor.GetPaintValueSupported(TypeDescriptorContext); + } + else + { + _button.Ellipsis = false; + } + + if (_button.Ellipsis) + { + EditControl.AccessibleRole = (IsReadOnly() ? AccessibleRole.StaticText : AccessibleRole.Text); + } + else + { + EditControl.AccessibleRole = (IsReadOnly() ? AccessibleRole.DropList : AccessibleRole.ComboBox); + } + + _button.TabStop = _button.Ellipsis; + _button.TabIndex = currentTabIndex++; + _button.AccessibleRole = (_button.Ellipsis ? AccessibleRole.PushButton : AccessibleRole.ButtonDropDown); + _button.AccessibleDescription = EditControl.AccessibleDescription; + _button.AccessibleName = EditControl.AccessibleName; + } + + protected override void OnReadOnlyTextBoxLabelClick(object sender, MouseEventArgs e) + { + base.OnReadOnlyTextBoxLabelClick(sender, e); + + if (e.Button == MouseButtons.Left) + { + if (ActionPanel.DropDownActive) + { + _ignoreDropDownValue = true; + CloseDropDown(); + } + else + { + ActivateDropDown(); + } + } + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + _swatch = null; + if (_hasSwatch) + { + ActionPanel.Invalidate(new Rectangle(EditRegionLocation, EditRegionSize), false); + } + } + + public override void PaintLine(Graphics g, int lineWidth, int lineHeight) + { + base.PaintLine(g, lineWidth, lineHeight); + + if (_hasSwatch) + { + if (_swatch == null) + { + int width = EditRegionSize.Height - EditorLineSwatchPadding * 2; + int height = width - 1; + _swatch = new Bitmap(width, height); + Rectangle rect = new Rectangle(1, 1, width - 2, height - 2); + using (Graphics swatchGraphics = Graphics.FromImage(_swatch)) + { + _editor.PaintValue(Value, swatchGraphics, rect); + swatchGraphics.DrawRectangle(SystemPens.ControlDark, new Rectangle(0, 0, width - 1, height - 1)); + } + } + + g.DrawImage(_swatch, new Point(EditRegionRelativeLocation.X + 2, EditorLineSwatchPadding + 5)); + } + } + + protected internal override bool ProcessDialogKey(Keys keyData) + { + // Do this here rather than in OnKeyDown because if hierarchy is properly set, VS is going to eat the F4 in PreProcessMessage, preventing it from ever getting to an OnKeyDown on this control. Doing it here also allow to not hook up to multiple events for each button. + if (!_button.Focused && !_button.Ellipsis) + { + + if ((keyData == (Keys.Alt | Keys.Down)) || + (keyData == (Keys.Alt | Keys.Up)) || + (keyData == Keys.F4)) + { + + if (!ActionPanel.DropDownActive) + { + ActivateDropDown(); + } + else + CloseDropDown(); + return true; + } + // Not passing Alt key event to base class to prevent closing 'Combobox Tasks window' + else if ((keyData & Keys.Alt) == Keys.Alt) + return true; + } + + return base.ProcessDialogKey(keyData); + } + + private void ShowDropDown(Control hostedControl, Color borderColor) + { + hostedControl.Width = Math.Max(hostedControl.Width, EditRegionSize.Width - 2); + _dropDownHolder = new DropDownHolder(hostedControl, ActionPanel, borderColor, ActionPanel.Font, this); + + if (ActionPanel.RightToLeft != RightToLeft.Yes) + { + Rectangle editorBounds = new Rectangle(Point.Empty, EditRegionSize); + Size dropDownSize = _dropDownHolder.Size; + Point editorLocation = ActionPanel.PointToScreen(EditRegionLocation); + Rectangle rectScreen = Screen.FromRectangle(ActionPanel.RectangleToScreen(editorBounds)).WorkingArea; + dropDownSize.Width = Math.Max(editorBounds.Width + 1, dropDownSize.Width); + + editorLocation.X = Math.Min(rectScreen.Right - dropDownSize.Width, // min = right screen edge clip + Math.Max(rectScreen.X, editorLocation.X + editorBounds.Right - dropDownSize.Width)); // max = left screen edge clip + editorLocation.Y += editorBounds.Y; + if (rectScreen.Bottom < (dropDownSize.Height + editorLocation.Y + editorBounds.Height)) + { + editorLocation.Y -= dropDownSize.Height + 1; + } + else + { + editorLocation.Y += editorBounds.Height; + } + + _dropDownHolder.Location = editorLocation; + } + else + { + _dropDownHolder.RightToLeft = ActionPanel.RightToLeft; + + Rectangle editorBounds = new Rectangle(Point.Empty, EditRegionSize); + Size dropDownSize = _dropDownHolder.Size; + Point editorLocation = ActionPanel.PointToScreen(EditRegionLocation); + Rectangle rectScreen = Screen.FromRectangle(ActionPanel.RectangleToScreen(editorBounds)).WorkingArea; + dropDownSize.Width = Math.Max(editorBounds.Width + 1, dropDownSize.Width); + + editorLocation.X = Math.Min(rectScreen.Right - dropDownSize.Width, // min = right screen edge clip + Math.Max(rectScreen.X, editorLocation.X - editorBounds.Width)); // max = left screen edge clip + editorLocation.Y += editorBounds.Y; + if (rectScreen.Bottom < (dropDownSize.Height + editorLocation.Y + editorBounds.Height)) + { + editorLocation.Y -= dropDownSize.Height + 1; + } + else + { + editorLocation.Y += editorBounds.Height; + } + + _dropDownHolder.Location = editorLocation; + } + + ActionPanel.InMethodInvoke = true; + ActionPanel.SetDropDownActive(true); + try + { + _dropDownHolder.ShowDropDown(_button); + } + finally + { + _button.ResetMouseStates(); + ActionPanel.SetDropDownActive(false); + ActionPanel.InMethodInvoke = false; + } + } + + #region IWindowsFormsEditorService implementation + void IWindowsFormsEditorService.CloseDropDown() + { + CloseDropDown(); + } + + void IWindowsFormsEditorService.DropDownControl(Control control) + { + ShowDropDown(control, ActionPanel.BorderColor); + } + + DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog) + { + IUIService uiService = (IUIService)ServiceProvider.GetService(typeof(IUIService)); + if (uiService != null) + { + return uiService.ShowDialog(dialog); + } + + return dialog.ShowDialog(); + } + #endregion + + #region IServiceProvider implementation + object IServiceProvider.GetService(Type serviceType) + { + // Inject this class as the IWindowsFormsEditroService so drop-down custom editors can work + if (serviceType == typeof(IWindowsFormsEditorService)) + { + return this; + } + + return ServiceProvider.GetService(serviceType); + } + #endregion + + private class DropDownHolder : FlyoutDialog + { + private readonly EditorPropertyLine _parent; + + public DropDownHolder(Control hostedControl, Control parentControl, Color borderColor, Font font, EditorPropertyLine parent) : base(hostedControl, parentControl, borderColor, font) + { + _parent = parent; + _parent.ActionPanel.SetDropDownActive(true); + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + _parent.ActionPanel.SetDropDownActive(false); + } + + protected override bool ProcessDialogKey(Keys keyData) + { + if (keyData == Keys.Escape) + { + // Indicates that the selection was aborted so we should ignore the value + _parent._ignoreDropDownValue = true; + Visible = false; + return true; + } + + return base.ProcessDialogKey(keyData); + } + } + + // Mostly copied from WinForms\Managed\System\WinForms\PropertyGridInternal\PropertyGridView.cs + internal class FlyoutDialog : Form + { + private readonly Control _hostedControl; + private readonly Control _parentControl; + + public FlyoutDialog(Control hostedControl, Control parentControl, Color borderColor, Font font) + { + _hostedControl = hostedControl; + _parentControl = parentControl; + + BackColor = SystemColors.Window; + ControlBox = false; + Font = font; + FormBorderStyle = FormBorderStyle.None; + MinimizeBox = false; + MaximizeBox = false; + ShowInTaskbar = false; + StartPosition = FormStartPosition.Manual; + Text = string.Empty; + + SuspendLayout(); + try + { + Controls.Add(hostedControl); + + int width = Math.Max(_hostedControl.Width, SystemInformation.MinimumWindowSize.Width); + int height = Math.Max(_hostedControl.Height, SystemInformation.MinimizedWindowSize.Height); + if (!borderColor.IsEmpty) + { + DockPadding.All = 1; + BackColor = borderColor; + width += 2; + height += 4; + } + _hostedControl.Dock = DockStyle.Fill; + + Width = width; + Height = height; + } + finally + { + ResumeLayout(); + } + } + + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.ExStyle |= NativeMethods.WS_EX_TOOLWINDOW; + cp.Style |= NativeMethods.WS_POPUP | NativeMethods.WS_BORDER; + cp.ClassStyle |= NativeMethods.CS_SAVEBITS; + + if (_parentControl != null) + { + if (!_parentControl.IsDisposed) + { + cp.Parent = _parentControl.Handle; + } + } + return cp; + } + } + + public virtual void FocusComponent() + { + if (_hostedControl != null && Visible) + { + _hostedControl.Focus(); + } + } + + // Lifted directly from PropertyGridView.DropDownHolder. Less destructive than using ShowDialog(). + public void DoModalLoop() + { + while (Visible) + { + Application.DoEvents(); + UnsafeNativeMethods.MsgWaitForMultipleObjectsEx(0, IntPtr.Zero, 250, NativeMethods.QS_ALLINPUT, NativeMethods.MWMO_INPUTAVAILABLE); + } + } + + /// + /// General purpose method, based on Control.Contains()... Determines whether a given window (specified using native window handle) is a descendant of this control. This catches both contained descendants and 'owned' windows such as modal dialogs. Using window handles rather than Control objects allows it to catch un-managed windows as well. + /// + private bool OwnsWindow(IntPtr hWnd) + { + while (hWnd != IntPtr.Zero) + { + hWnd = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hWnd), NativeMethods.GWL_HWNDPARENT); + if (hWnd == IntPtr.Zero) + { + return false; + } + if (hWnd == Handle) + { + return true; + } + } + return false; + } + + protected override bool ProcessDialogKey(Keys keyData) + { + if ((keyData == (Keys.Alt | Keys.Down)) || + (keyData == (Keys.Alt | Keys.Up)) || + (keyData == Keys.F4)) + { + // Any of these keys indicates the selection is accepted + Visible = false; + return true; + } + return base.ProcessDialogKey(keyData); + } + + public void ShowDropDown(Control parent) + { + try + { + UnsafeNativeMethods.SetWindowLong(new HandleRef(this, Handle), NativeMethods.GWL_HWNDPARENT, new HandleRef(parent, parent.Handle)); + IntPtr hWndCapture = UnsafeNativeMethods.GetCapture(); + if (hWndCapture != IntPtr.Zero) + { + UnsafeNativeMethods.SendMessage(new HandleRef(null, hWndCapture), NativeMethods.WM_CANCELMODE, 0, 0); + SafeNativeMethods.ReleaseCapture(); + } + + Visible = true; + FocusComponent(); + DoModalLoop(); + } + finally + { + UnsafeNativeMethods.SetWindowLong(new HandleRef(this, Handle), NativeMethods.GWL_HWNDPARENT, new HandleRef(null, IntPtr.Zero)); + + // sometimes activation gets messed up - if our parent control is still around, remind it to take focus. + if (parent != null && parent.Visible) + { + parent.Focus(); + } + } + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == NativeMethods.WM_ACTIVATE) + { + if (Visible && NativeMethods.Util.LOWORD(unchecked((int)(long)m.WParam)) == NativeMethods.WA_INACTIVE) + { + if (!OwnsWindow((IntPtr)m.LParam)) + { + Visible = false; + if (m.LParam == IntPtr.Zero) + { //we 're switching process also dismiss the parent + Control toplevel = _parentControl.TopLevelControl; + if (toplevel is ToolStripDropDown dropDown) + { + // if it's a toolstrip dropdown let it know that we have a specific close reason. + dropDown.Close(); + } + else if (toplevel != null) + { + toplevel.Visible = false; + } + } + return; + } + } + } + base.WndProc(ref m); + } + } + + #region Interop definitions + private static class NativeMethods + { + public const int WM_ACTIVATE = 0x0006, + WM_CANCELMODE = 0x001F, + WM_MOUSEACTIVATE = 0x0021, + WM_NCLBUTTONDOWN = 0x00A1, + WM_NCRBUTTONDOWN = 0x00A4, + WM_NCMBUTTONDOWN = 0x00A7, + WM_LBUTTONDOWN = 0x0201, + WM_RBUTTONDOWN = 0x0204, + WM_MBUTTONDOWN = 0x0207, + WA_INACTIVE = 0, + WA_ACTIVE = 1, + WS_EX_TOOLWINDOW = 0x00000080, + WS_POPUP = unchecked((int)0x80000000), + WS_BORDER = 0x00800000, + GWL_HWNDPARENT = (-8), + QS_KEY = 0x0001, + QS_MOUSEMOVE = 0x0002, + QS_MOUSEBUTTON = 0x0004, + QS_POSTMESSAGE = 0x0008, + QS_TIMER = 0x0010, + QS_PAINT = 0x0020, + QS_SENDMESSAGE = 0x0040, + QS_HOTKEY = 0x0080, + QS_ALLPOSTMESSAGE = 0x0100, + QS_MOUSE = QS_MOUSEMOVE | QS_MOUSEBUTTON, + QS_INPUT = QS_MOUSE | QS_KEY, + QS_ALLEVENTS = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY, + QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE, + CS_SAVEBITS = 0x0800; + + public const int MWMO_INPUTAVAILABLE = 0x0004; // don't use MWMO_WAITALL, see ddb#176342 + + internal static class Util + { + public static int LOWORD(int n) + { + return n & 0xffff; + } + } + + public static class CommonHandles + { + public static HandleCollector s_gdiHandleCollector = new HandleCollector("GDI", 500); + public static HandleCollector s_hdcHandleCollector = new HandleCollector("HDC", 2); + } + + [StructLayout(LayoutKind.Sequential)] + public class SIZE + { + public int cx = 0; + public int cy = 0; + + public SIZE() + { + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct TEXTMETRIC + { + public int tmHeight; + public int tmAscent; + public int tmDescent; + public int tmInternalLeading; + public int tmExternalLeading; + public int tmAveCharWidth; + public int tmMaxCharWidth; + public int tmWeight; + public int tmOverhang; + public int tmDigitizedAspectX; + public int tmDigitizedAspectY; + public char tmFirstChar; + public char tmLastChar; + public char tmDefaultChar; + public char tmBreakChar; + public byte tmItalic; + public byte tmUnderlined; + public byte tmStruckOut; + public byte tmPitchAndFamily; + public byte tmCharSet; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct TEXTMETRICA + { + public int tmHeight; + public int tmAscent; + public int tmDescent; + public int tmInternalLeading; + public int tmExternalLeading; + public int tmAveCharWidth; + public int tmMaxCharWidth; + public int tmWeight; + public int tmOverhang; + public int tmDigitizedAspectX; + public int tmDigitizedAspectY; + public byte tmFirstChar; + public byte tmLastChar; + public byte tmDefaultChar; + public byte tmBreakChar; + public byte tmItalic; + public byte tmUnderlined; + public byte tmStruckOut; + public byte tmPitchAndFamily; + public byte tmCharSet; + } + } + + private static class SafeNativeMethods + { + [DllImport(ExternDllGdi32, SetLastError = true, ExactSpelling = true, EntryPoint = "DeleteObject", CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + private static extern bool IntDeleteObject(HandleRef hObject); + public static bool DeleteObject(HandleRef hObject) + { + NativeMethods.CommonHandles.s_gdiHandleCollector.Remove(); + return IntDeleteObject(hObject); + } + + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool ReleaseCapture(); + + [DllImport(ExternDllGdi32, SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SelectObject(HandleRef hDC, HandleRef hObject); + + [DllImport(ExternDllGdi32, SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetTextExtentPoint32(HandleRef hDC, string str, int len, [In, Out] NativeMethods.SIZE size); + + [DllImport(ExternDllGdi32, SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetTextMetricsW(HandleRef hDC, [In, Out] ref NativeMethods.TEXTMETRIC lptm); + + [DllImport(ExternDllGdi32, SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi)] + [ResourceExposure(ResourceScope.None)] + public static extern int GetTextMetricsA(HandleRef hDC, [In, Out] ref NativeMethods.TEXTMETRICA lptm); + + public static int GetTextMetrics(HandleRef hDC, ref NativeMethods.TEXTMETRIC lptm) + { + if (Marshal.SystemDefaultCharSize == 1) + { + // ANSI + NativeMethods.TEXTMETRICA lptmA = new NativeMethods.TEXTMETRICA(); + int retVal = SafeNativeMethods.GetTextMetricsA(hDC, ref lptmA); + + lptm.tmHeight = lptmA.tmHeight; + lptm.tmAscent = lptmA.tmAscent; + lptm.tmDescent = lptmA.tmDescent; + lptm.tmInternalLeading = lptmA.tmInternalLeading; + lptm.tmExternalLeading = lptmA.tmExternalLeading; + lptm.tmAveCharWidth = lptmA.tmAveCharWidth; + lptm.tmMaxCharWidth = lptmA.tmMaxCharWidth; + lptm.tmWeight = lptmA.tmWeight; + lptm.tmOverhang = lptmA.tmOverhang; + lptm.tmDigitizedAspectX = lptmA.tmDigitizedAspectX; + lptm.tmDigitizedAspectY = lptmA.tmDigitizedAspectY; + lptm.tmFirstChar = (char)lptmA.tmFirstChar; + lptm.tmLastChar = (char)lptmA.tmLastChar; + lptm.tmDefaultChar = (char)lptmA.tmDefaultChar; + lptm.tmBreakChar = (char)lptmA.tmBreakChar; + lptm.tmItalic = lptmA.tmItalic; + lptm.tmUnderlined = lptmA.tmUnderlined; + lptm.tmStruckOut = lptmA.tmStruckOut; + lptm.tmPitchAndFamily = lptmA.tmPitchAndFamily; + lptm.tmCharSet = lptmA.tmCharSet; + + return retVal; + } + else + { + // Unicode + return SafeNativeMethods.GetTextMetricsW(hDC, ref lptm); + } + } + } + + private static class UnsafeNativeMethods + { + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr GetWindowLong(HandleRef hWnd, int nIndex); + + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SetWindowLong(HandleRef hWnd, int nIndex, HandleRef dwNewLong); + + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int MsgWaitForMultipleObjectsEx(int nCount, IntPtr pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags); + + [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable")] + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam); + + [DllImport(ExternDllUser32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr GetCapture(); + + [DllImport(ExternDllUser32, ExactSpelling = true, EntryPoint = "GetDC", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.Process)] + private static extern IntPtr IntGetDC(HandleRef hWnd); + public static IntPtr GetDC(HandleRef hWnd) + { + NativeMethods.CommonHandles.s_hdcHandleCollector.Add(); + return IntGetDC(hWnd); + } + + [DllImport(ExternDllUser32, ExactSpelling = true, EntryPoint = "ReleaseDC", CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + private static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC); + public static int ReleaseDC(HandleRef hWnd, HandleRef hDC) + { + NativeMethods.CommonHandles.s_hdcHandleCollector.Remove(); + return IntReleaseDC(hWnd, hDC); + } + } + #endregion + + // Class that renders either the ellipsis or dropdown button + internal sealed class EditorButton : Button + { + private bool _mouseOver; + private bool _mouseDown; + private bool _ellipsis; + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + if (e.Button == MouseButtons.Left) + { + _mouseDown = true; + } + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + _mouseOver = true; + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + _mouseOver = false; + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (e.Button == MouseButtons.Left) + { + _mouseDown = false; + } + } + + public bool Ellipsis { + get => _ellipsis; + set => _ellipsis = value; + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + if (_ellipsis) + { + PushButtonState buttonState = PushButtonState.Normal; + if (_mouseDown) + { + buttonState = PushButtonState.Pressed; + } + else if (_mouseOver) + { + buttonState = PushButtonState.Hot; + } + + ButtonRenderer.DrawButton(g, new Rectangle(-1, -1, Width + 2, Height + 2), "…", Font, Focused, buttonState); + } + else + { + if (ComboBoxRenderer.IsSupported) + { + ComboBoxState state = ComboBoxState.Normal; + + if (Enabled) + { + if (_mouseDown) + { + state = ComboBoxState.Pressed; + } + else if (_mouseOver) + { + state = ComboBoxState.Hot; + } + } + else + { + state = ComboBoxState.Disabled; + } + + ComboBoxRenderer.DrawDropDownButton(g, new Rectangle(0, 0, Width, Height), state); + } + else + { + PushButtonState buttonState = PushButtonState.Normal; + + if (Enabled) + { + if (_mouseDown) + { + buttonState = PushButtonState.Pressed; + } + else if (_mouseOver) + { + buttonState = PushButtonState.Hot; + } + } + else + { + buttonState = PushButtonState.Disabled; + } + ButtonRenderer.DrawButton(g, new Rectangle(-1, -1, Width + 2, Height + 2), string.Empty, Font, Focused, buttonState); + + // Draw the arrow icon + try + { + Icon icon = new Icon(typeof(DesignerActionPanel), "Arrow.ico"); + try + { + Bitmap arrowBitmap = icon.ToBitmap(); + // Make sure we draw properly under high contrast by re-mapping the arrow color to the WindowText color + ImageAttributes attrs = new ImageAttributes(); + + try + { + ColorMap cm = new ColorMap + { + OldColor = Color.Black, + NewColor = SystemColors.WindowText + }; + + attrs.SetRemapTable(new ColorMap[] { cm }, ColorAdjustType.Bitmap); + + int imageWidth = arrowBitmap.Width; + int imageHeight = arrowBitmap.Height; + g.DrawImage(arrowBitmap, new Rectangle((Width - imageWidth + 1) / 2, (Height - imageHeight + 1) / 2, imageWidth, imageHeight), + 0, 0, imageWidth, imageWidth, + GraphicsUnit.Pixel, attrs, null, IntPtr.Zero); + } + + finally + { + if (attrs != null) + { + attrs.Dispose(); + } + } + } + + finally + { + if (icon != null) + { + icon.Dispose(); + } + } + } + catch + { + } + } + + if (Focused) + { + ControlPaint.DrawFocusRectangle(g, new Rectangle(2, 2, Width - 5, Height - 5)); + } + } + } + + public void ResetMouseStates() + { + _mouseDown = false; + _mouseOver = false; + + Invalidate(); + } + } + } + + private class TextLine : Line + { + private Label _label; + private DesignerActionTextItem _textItem; + + public TextLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) + : base(serviceProvider, actionPanel) + { + actionPanel.FontChanged += new EventHandler(OnParentControlFontChanged); + } + + public sealed override string FocusId + { + get + { + return string.Empty; + } + } + + protected override void AddControls(List controls) + { + _label = new Label + { + BackColor = Color.Transparent, + ForeColor = ActionPanel.LabelForeColor, + TextAlign = Drawing.ContentAlignment.MiddleLeft, + UseMnemonic = false + }; + + controls.Add(_label); + } + + public sealed override void Focus() + { + Debug.Fail("Should never try to focus a TextLine"); + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + Size labelSize = _label.GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + + if (!measureOnly) + { + _label.Location = new Point(LineLeftMargin, top + LineVerticalPadding / 2); + _label.Size = labelSize; + } + + return labelSize + new Size(LineLeftMargin + LineRightMargin, LineVerticalPadding); + } + + private void OnParentControlFontChanged(object sender, EventArgs e) + { + if (_label != null && _label.Font != null) + { + _label.Font = GetFont(); + } + } + + protected virtual Font GetFont() + { + return ActionPanel.Font; + } + + internal override void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex) + { + _textItem = (DesignerActionTextItem)actionItem; + + _label.Text = StripAmpersands(_textItem.DisplayName); + _label.Font = GetFont(); + _label.TabIndex = currentTabIndex++; + + toolTip.SetToolTip(_label, _textItem.Description); + } + } + + private sealed class HeaderLine : TextLine + { + public HeaderLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) + : base(serviceProvider, actionPanel) + { + } + + protected override Font GetFont() + { + return new Font(ActionPanel.Font, FontStyle.Bold); + } + } + + private sealed class SeparatorLine : Line + { + private readonly bool _isSubSeparator; + + public SeparatorLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : this(serviceProvider, actionPanel, false) + { + } + + public SeparatorLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel, bool isSubSeparator) : base(serviceProvider, actionPanel) + { + _isSubSeparator = isSubSeparator; + } + + public sealed override string FocusId + { + get => string.Empty; + } + + public bool IsSubSeparator => _isSubSeparator; + + protected override void AddControls(System.Collections.Generic.List controls) + { + } + + public sealed override void Focus() + { + Debug.Fail("Should never try to focus a SeparatorLine"); + } + + public override Size LayoutControls(int top, int width, bool measureOnly) + { + return new Size(MinimumWidth, 1); + } + + public override void PaintLine(Graphics g, int lineWidth, int lineHeight) + { + using (Pen p = new Pen(ActionPanel.SeparatorColor)) + { + g.DrawLine(p, SeparatorHorizontalPadding, 0, lineWidth - (SeparatorHorizontalPadding + 1), 0); + } + } + + internal override void UpdateActionItem(DesignerActionList actionList, DesignerActionItem actionItem, ToolTip toolTip, ref int currentTabIndex) + { + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionService.cs new file mode 100644 index 00000000000..cb4476759c2 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionService.cs @@ -0,0 +1,465 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Diagnostics; +using System.Windows.Forms.Design; + +namespace System.ComponentModel.Design +{ + /// + /// The DesignerActionService manages DesignerActions. All DesignerActions are associated with an object. DesignerActions can be added or removed at any given time. + /// The DesignerActionService controls the expiration of DesignerActions by monitoring three basic events: selection change, component change, and timer expiration. + /// Designer implementing this service will need to monitor the DesignerActionsChanged event on this class. This event will fire every time a change is made to any object's DesignerActions. + /// + public class DesignerActionService : IDisposable + { + private readonly Hashtable _designerActionLists; //this is how we store 'em. Syntax: key = object, value = DesignerActionListCollection + private DesignerActionListsChangedEventHandler _designerActionListsChanged; + private readonly IServiceProvider _serviceProvider; //standard service provider + private readonly ISelectionService _selSvc; // selection service + private readonly Hashtable _componentToVerbsEventHookedUp; //table component true/false + // Gaurd against ReEntrant Code. The Infragistics TabControlDesigner, Sets the Commands Status when the Verbs property is accesssed. This property is used in the OnVerbStatusChanged code here and hence causes recursion leading to Stack Overflow Exception. + private bool _reEntrantCode = false; + + /// + /// Standard constructor. A Service Provider is necessary for monitoring selection and component changes. + /// + public DesignerActionService(IServiceProvider serviceProvider) + { + + if (serviceProvider != null) + { + _serviceProvider = serviceProvider; + IDesignerHost host = (IDesignerHost)serviceProvider.GetService(typeof(IDesignerHost)); + host.AddService(typeof(DesignerActionService), this); + IComponentChangeService cs = (IComponentChangeService)serviceProvider.GetService(typeof(IComponentChangeService)); + if (cs != null) + { + cs.ComponentRemoved += new ComponentEventHandler(OnComponentRemoved); + } + _selSvc = (ISelectionService)serviceProvider.GetService(typeof(ISelectionService)); + if (_selSvc == null) + { + Debug.Fail("Either BehaviorService or ISelectionService is null, cannot continue."); + } + } + _designerActionLists = new Hashtable(); + _componentToVerbsEventHookedUp = new Hashtable(); +#if DEBUGDESIGNERTASKS + Debug.WriteLine("DesignerActionService - created"); +#endif + } + + /// + /// This event is thrown whenever a DesignerActionList is removed or added for any object. + /// + public event DesignerActionListsChangedEventHandler DesignerActionListsChanged + { + add + { + _designerActionListsChanged += value; + } + remove + { + _designerActionListsChanged -= value; + } + } + + /// + /// Adds a new collection of DesignerActions to be monitored with the related comp object. + /// + public void Add(IComponent comp, DesignerActionListCollection designerActionListCollection) + { + if (comp == null) + { + throw new ArgumentNullException(nameof(comp)); + } + if (designerActionListCollection == null) + { + throw new ArgumentNullException(nameof(designerActionListCollection)); + } + + DesignerActionListCollection dhlc = (DesignerActionListCollection)_designerActionLists[comp]; + if (dhlc != null) + { + dhlc.AddRange(designerActionListCollection); + } + else + { + _designerActionLists.Add(comp, designerActionListCollection); + } + OnDesignerActionListsChanged(new DesignerActionListsChangedEventArgs(comp, DesignerActionListsChangedType.ActionListsAdded, GetComponentActions(comp))); + } + + /// + /// Adds a new DesignerActionList to be monitored with the related comp object + /// + public void Add(IComponent comp, DesignerActionList actionList) + { + Add(comp, new DesignerActionListCollection( new [] { actionList } )); + } + + /// + /// Clears all objects and DesignerActions from the DesignerActionService. + /// + public void Clear() + { + + if (_designerActionLists.Count == 0) + { + return; + } + + //this will represent the list of componets we just cleared + ArrayList compsRemoved = new ArrayList(_designerActionLists.Count); + foreach (DictionaryEntry entry in _designerActionLists) + { + compsRemoved.Add(entry.Key); + } + + //actually clear our hashtable + _designerActionLists.Clear(); + + //fire our DesignerActionsChanged event for each comp we just removed + foreach (Component comp in compsRemoved) + { + OnDesignerActionListsChanged(new DesignerActionListsChangedEventArgs(comp, DesignerActionListsChangedType.ActionListsRemoved, GetComponentActions(comp))); + } + + } + + /// + /// Returns true if the DesignerActionService is currently managing the comp object. + /// + public bool Contains(IComponent comp) + { + if (comp == null) + { + throw new ArgumentNullException(nameof(comp)); + } + return _designerActionLists.Contains(comp); + } + + /// + /// Disposes all resources and unhooks all events. + /// + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _serviceProvider != null) + { + IDesignerHost host = (IDesignerHost)_serviceProvider.GetService(typeof(IDesignerHost)); + if (host != null) + { + host.RemoveService(typeof(DesignerActionService)); + } + + IComponentChangeService cs = (IComponentChangeService)_serviceProvider.GetService(typeof(IComponentChangeService)); + if (cs != null) + { + cs.ComponentRemoved -= new ComponentEventHandler(OnComponentRemoved); + } + } + } + + public DesignerActionListCollection GetComponentActions(IComponent component) + { + return GetComponentActions(component, ComponentActionsType.All); + } + + + public virtual DesignerActionListCollection GetComponentActions(IComponent component, ComponentActionsType type) + { + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + DesignerActionListCollection result = new DesignerActionListCollection(); + switch (type) + { + case ComponentActionsType.All: + GetComponentDesignerActions(component, result); + GetComponentServiceActions(component, result); + break; + case ComponentActionsType.Component: + GetComponentDesignerActions(component, result); + break; + case ComponentActionsType.Service: + GetComponentServiceActions(component, result); + break; + + } + return result; + } + + protected virtual void GetComponentDesignerActions(IComponent component, DesignerActionListCollection actionLists) + { + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + + if (actionLists == null) + { + throw new ArgumentNullException(nameof(actionLists)); + } + + if (component.Site is IServiceContainer sc) + { + DesignerCommandSet dcs = (DesignerCommandSet)sc.GetService(typeof(DesignerCommandSet)); + if (dcs != null) + { + DesignerActionListCollection pullCollection = dcs.ActionLists; + if (pullCollection != null) + { + actionLists.AddRange(pullCollection); + } + + // if we don't find any, add the verbs for this component there... + if (actionLists.Count == 0) + { + DesignerVerbCollection verbs = dcs.Verbs; + if (verbs != null && verbs.Count != 0) + { + ArrayList verbsArray = new ArrayList(); + bool hookupEvents = _componentToVerbsEventHookedUp[component] == null; + if (hookupEvents) + { + _componentToVerbsEventHookedUp[component] = true; + } + foreach (DesignerVerb verb in verbs) + { + if (hookupEvents) + { + //Debug.WriteLine("hooking up change event for verb " + verb.Text); + verb.CommandChanged += new EventHandler(OnVerbStatusChanged); + } + if (verb.Enabled && verb.Visible) + { + //Debug.WriteLine("adding verb to collection for panel... " + verb.Text); + verbsArray.Add(verb); + } + } + if (verbsArray.Count != 0) + { + DesignerActionVerbList davl = new DesignerActionVerbList((DesignerVerb[])verbsArray.ToArray(typeof(DesignerVerb))); + actionLists.Add(davl); + } + } + } + + // remove all the ones that are empty... ie GetSortedActionList returns nothing we might waste some time doing this twice but don't have much of a choice here... the panel is not yet displayed and we want to know if a non empty panel is present... + // NOTE: We do this AFTER the verb check that way to disable auto verb upgrading you can just return an empty actionlist collection + if (pullCollection != null) + { + foreach (DesignerActionList actionList in pullCollection) + { + DesignerActionItemCollection collection = actionList.GetSortedActionItems(); + if (collection == null || collection.Count == 0) + { + actionLists.Remove(actionList); + } + } + } + } + } + } + + private void OnVerbStatusChanged(object sender, EventArgs args) + { + if (!_reEntrantCode) + { + try + { + _reEntrantCode = true; + if (_selSvc.PrimarySelection is IComponent comp) + { + if (comp.Site is IServiceContainer sc) + { + DesignerCommandSet dcs = (DesignerCommandSet)sc.GetService(typeof(DesignerCommandSet)); + foreach (DesignerVerb verb in dcs.Verbs) + { + if (verb == sender) + { + DesignerActionUIService dapUISvc = (DesignerActionUIService)sc.GetService(typeof(DesignerActionUIService)); + if (dapUISvc != null) + { + //Debug.WriteLine("Calling refresh on component " + comp.Site.Name); + dapUISvc.Refresh(comp); // we need to refresh, a verb on the current panel has changed its state + } + } + } + } + } + } + finally + { + _reEntrantCode = false; + } + } + } + + protected virtual void GetComponentServiceActions(IComponent component, DesignerActionListCollection actionLists) + { + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + + if (actionLists == null) + { + throw new ArgumentNullException(nameof(actionLists)); + } + + DesignerActionListCollection pushCollection = (DesignerActionListCollection)_designerActionLists[component]; + if (pushCollection != null) + { + actionLists.AddRange(pushCollection); + // remove all the ones that are empty... ie GetSortedActionList returns nothing we might waste some time doing this twice but don't have much of a choice here... the panel is not yet displayed and we want to know if a non empty panel is present... + foreach (DesignerActionList actionList in pushCollection) + { + DesignerActionItemCollection collection = actionList.GetSortedActionItems(); + if (collection == null || collection.Count == 0) + { + actionLists.Remove(actionList); + } + } + } + } + + /// + /// We hook the OnComponentRemoved event so we can clean up all associated actions. + /// + private void OnComponentRemoved(object source, ComponentEventArgs ce) + { + Remove(ce.Component); + } + + /// + /// This fires our DesignerActionsChanged event. + /// + private void OnDesignerActionListsChanged(DesignerActionListsChangedEventArgs e) + { + _designerActionListsChanged?.Invoke(this, e); + } + + /// + /// This will remove all DesignerActions associated with the 'comp' object. All alarms will be unhooked and the DesignerActionsChagned event will be fired. + /// + public void Remove(IComponent comp) + { + if (comp == null) + { + throw new ArgumentNullException(nameof(comp)); + } + + if (!_designerActionLists.Contains(comp)) + { + return; + } + + _designerActionLists.Remove(comp); + + OnDesignerActionListsChanged(new DesignerActionListsChangedEventArgs(comp, DesignerActionListsChangedType.ActionListsRemoved, GetComponentActions(comp))); + } + + /// + /// This will remove the specified Designeraction from the DesignerActionService. All alarms will be unhooked and the DesignerActionsChagned event will be fired. + /// + public void Remove(DesignerActionList actionList) + { + if (actionList == null) + { + throw new ArgumentNullException(nameof(actionList)); + } + + //find the associated component + foreach (IComponent comp in _designerActionLists.Keys) + { + if (((DesignerActionListCollection)_designerActionLists[comp]).Contains(actionList)) + { + Remove(comp, actionList); + break; + } + } + } + + /// + /// This will remove the all instances of the DesignerAction from the 'comp' object. If an alarm was set, it will be unhooked. This will also fire the DesignerActionChanged event. + /// + public void Remove(IComponent comp, DesignerActionList actionList) + { + if (comp == null) + { + throw new ArgumentNullException(nameof(comp)); + } + if (actionList == null) + { + throw new ArgumentNullException(nameof(actionList)); + } + if (!_designerActionLists.Contains(comp)) + { + return; + } + + DesignerActionListCollection actionLists = (DesignerActionListCollection)_designerActionLists[comp]; + + if (!actionLists.Contains(actionList)) + { + return; + } + + if (actionLists.Count == 1) + { + //this is the last action for this object, remove the entire thing + Remove(comp); + } + else + { + //remove each instance of this action + ArrayList actionListsToRemove = new ArrayList(1); + foreach (DesignerActionList t in actionLists) + { + if (actionList.Equals(t)) + { + //found one to remove + actionListsToRemove.Add(t); + } + } + + foreach (DesignerActionList t in actionListsToRemove) + { + actionLists.Remove(t); + } + + OnDesignerActionListsChanged(new DesignerActionListsChangedEventArgs(comp, DesignerActionListsChangedType.ActionListsRemoved, GetComponentActions(comp))); + } + } + + internal event DesignerActionUIStateChangeEventHandler DesignerActionUIStateChange + { + add + { + DesignerActionUIService dapUISvc = (DesignerActionUIService)_serviceProvider.GetService(typeof(DesignerActionUIService)); + if (dapUISvc != null) + { + dapUISvc.DesignerActionUIStateChange += value; + } + } + remove + { + DesignerActionUIService dapUISvc = (DesignerActionUIService)_serviceProvider.GetService(typeof(DesignerActionUIService)); + if (dapUISvc != null) + { + dapUISvc.DesignerActionUIStateChange -= value; + } + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionTextItem.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionTextItem.cs new file mode 100644 index 00000000000..61607c58347 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionTextItem.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + internal class DesignerActionTextItem : DesignerActionItem + { + public DesignerActionTextItem(string displayName, string category) : base(displayName, category, null) + { + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUI.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUI.cs new file mode 100644 index 00000000000..b5baccfd107 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUI.cs @@ -0,0 +1,1146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms.Design.Behavior; + +namespace System.Windows.Forms.Design +{ + /// + /// The DesignerActionUI is the designer/UI-specific implementation of the DesignerActions feature. This class instantiates the DesignerActionService and hooks to its DesignerActionsChanged event. + /// Responding to this single event will enable the DesignerActionUI to perform all neceessary UI-related operations.Note that the DesignerActionUI uses the BehaviorService to manage all UI + /// interaction. + /// For every component containing a DesignerAction (determined by the DesignerActionsChagned event) there will be an associated DesignerActionGlyph and DesignerActionBehavior. + /// Finally, the DesignerActionUI is also responsible for showing and managing the Action's context menus. Note that every DesignerAction context menu has an item that will bring up the DesignerActions option pane in the options dialog. + /// + internal class DesignerActionUI : IDisposable + { + private static readonly TraceSwitch s_designeActionPanelTraceSwitch = new TraceSwitch("DesigneActionPanelTrace", "DesignerActionPanel tracing"); + + private Adorner _designerActionAdorner; //used to add designeraction-related glyphs + private IServiceProvider _serviceProvider; //standard service provider + private ISelectionService _selSvc; //used to determine if comps have selection or not + private DesignerActionService _designerActionService; //this is how all designeractions will be managed + private DesignerActionUIService _designerActionUIService; //this is how all designeractions UI elements will be managed + private BehaviorService _behaviorService; //this is how all of our UI is implemented (glyphs, behaviors, etc...) + private readonly IMenuCommandService _menuCommandService; + private DesignerActionKeyboardBehavior _dapkb; //out keyboard behavior + private readonly Hashtable _componentToGlyph; //used for quick reference between compoments and our glyphs + private Control _marshalingControl; //used to invoke events on our main gui thread + private IComponent _lastPanelComponent; + + private readonly IUIService _uiService; + private readonly IWin32Window _mainParentWindow; + internal DesignerActionToolStripDropDown _designerActionHost; + + private readonly MenuCommand _cmdShowDesignerActions; //used to respond to the Alt+Shft+F10 command + private bool _inTransaction = false; + private IComponent _relatedComponentTransaction; + private DesignerActionGlyph _relatedGlyphTransaction; + private readonly bool _disposeActionService; + private readonly bool _disposeActionUIService; + + private delegate void ActionChangedEventHandler(object sender, DesignerActionListsChangedEventArgs e); + +#if DEBUG + internal static readonly TraceSwitch s_dropDownVisibilityDebug = new TraceSwitch("DropDownVisibilityDebug", "Debug ToolStrip Selection code"); +#else + internal static readonly TraceSwitch s_dropDownVisibilityDebug; +#endif + + /// + /// Constructor that takes a service provider. This is needed to establish + /// references to the BehaviorService and SelecteionService, as well as + /// spin-up the DesignerActionService. + /// + public DesignerActionUI(IServiceProvider serviceProvider, Adorner containerAdorner) + { + _serviceProvider = serviceProvider; + _designerActionAdorner = containerAdorner; + + _behaviorService = (BehaviorService)serviceProvider.GetService(typeof(BehaviorService)); + _menuCommandService = (IMenuCommandService)serviceProvider.GetService(typeof(IMenuCommandService)); + _selSvc = (ISelectionService)serviceProvider.GetService(typeof(ISelectionService)); + + if (_behaviorService == null || _selSvc == null) + { + Debug.Fail("Either BehaviorService or ISelectionService is null, cannot continue."); + return; + } + + //query for our DesignerActionService + _designerActionService = (DesignerActionService)serviceProvider.GetService(typeof(DesignerActionService)); + if (_designerActionService == null) + { + //start the service + _designerActionService = new DesignerActionService(serviceProvider); + _disposeActionService = true; + } + _designerActionUIService = (DesignerActionUIService)serviceProvider.GetService(typeof(DesignerActionUIService)); + if (_designerActionUIService == null) + { + _designerActionUIService = new DesignerActionUIService(serviceProvider); + _disposeActionUIService = true; + } + _designerActionUIService.DesignerActionUIStateChange += new DesignerActionUIStateChangeEventHandler(OnDesignerActionUIStateChange); + _designerActionService.DesignerActionListsChanged += new DesignerActionListsChangedEventHandler(OnDesignerActionsChanged); + _lastPanelComponent = null; + + IComponentChangeService cs = (IComponentChangeService)serviceProvider.GetService(typeof(IComponentChangeService)); + if (cs != null) + { + cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged); + } + + + if (_menuCommandService != null) + { + _cmdShowDesignerActions = new MenuCommand(new EventHandler(OnKeyShowDesignerActions), MenuCommands.KeyInvokeSmartTag); + _menuCommandService.AddCommand(_cmdShowDesignerActions); + } + + _uiService = (IUIService)serviceProvider.GetService(typeof(IUIService)); + if (_uiService != null) + _mainParentWindow = _uiService.GetDialogOwnerWindow(); + + _componentToGlyph = new Hashtable(); + + _marshalingControl = new Control(); + _marshalingControl.CreateControl(); + } + + /// + /// Disposes all UI-related objects and unhooks services. + /// + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")] + public void Dispose() + { + if (_marshalingControl != null) + { + _marshalingControl.Dispose(); + _marshalingControl = null; + } + + if (_serviceProvider != null) + { + IComponentChangeService cs = (IComponentChangeService)_serviceProvider.GetService(typeof(IComponentChangeService)); + if (cs != null) + { + cs.ComponentChanged -= new ComponentChangedEventHandler(OnComponentChanged); + } + + if (_cmdShowDesignerActions != null) + { + IMenuCommandService mcs = (IMenuCommandService)_serviceProvider.GetService(typeof(IMenuCommandService)); + if (mcs != null) + { + mcs.RemoveCommand(_cmdShowDesignerActions); + } + } + } + + _serviceProvider = null; + _behaviorService = null; + _selSvc = null; + + if (_designerActionService != null) + { + _designerActionService.DesignerActionListsChanged -= new DesignerActionListsChangedEventHandler(OnDesignerActionsChanged); + if (_disposeActionService) + { + _designerActionService.Dispose(); + } + } + _designerActionService = null; + + if (_designerActionUIService != null) + { + _designerActionUIService.DesignerActionUIStateChange -= new DesignerActionUIStateChangeEventHandler(OnDesignerActionUIStateChange); + if (_disposeActionUIService) + { + _designerActionUIService.Dispose(); + } + } + _designerActionUIService = null; + _designerActionAdorner = null; + } + + public DesignerActionGlyph GetDesignerActionGlyph(IComponent comp) + { + return GetDesignerActionGlyph(comp, null); + } + + internal DesignerActionGlyph GetDesignerActionGlyph(IComponent comp, DesignerActionListCollection dalColl) + { + // check this component origin, this class or is it readyonly because inherited... + InheritanceAttribute attribute = (InheritanceAttribute)TypeDescriptor.GetAttributes(comp)[typeof(InheritanceAttribute)]; + if (attribute == InheritanceAttribute.InheritedReadOnly) + { // only do it if we can change the control... + return null; + } + + // we didnt get on, fetch it + if (dalColl == null) + { + dalColl = _designerActionService.GetComponentActions(comp); + } + + if (dalColl != null && dalColl.Count > 0) + { + DesignerActionGlyph dag = null; + if (_componentToGlyph[comp] == null) + { + DesignerActionBehavior dab = new DesignerActionBehavior(_serviceProvider, comp, dalColl, this); + + // if comp is a component then try to find a traycontrol associated with it... this should really be in ComponentTray but there is no behaviorService for the CT + if (!(comp is Control) || comp is ToolStripDropDown) + { + //Here, we'll try to get the traycontrol associated with the comp and supply the glyph with an alternative bounds + if (_serviceProvider.GetService(typeof(ComponentTray)) is ComponentTray compTray) + { + ComponentTray.TrayControl trayControl = compTray.GetTrayControlFromComponent(comp); + if (trayControl != null) + { + Rectangle trayBounds = trayControl.Bounds; + dag = new DesignerActionGlyph(dab, trayBounds, compTray); + } + } + } + + // either comp is a control or we failed to find a traycontrol (which could be the case for toolstripitem components) - in this case just create a standard glyoh. + if (dag == null) + { + //if the related comp is a control, then this shortcut will just hang off its bounds + dag = new DesignerActionGlyph(dab, _designerActionAdorner); + } + + if (dag != null) + { + //store off this relationship + _componentToGlyph.Add(comp, dag); + } + } + else + { + dag = _componentToGlyph[comp] as DesignerActionGlyph; + if (dag != null) + { + if (dag.Behavior is DesignerActionBehavior behavior) + { + behavior.ActionLists = dalColl; + } + dag.Invalidate(); // need to invalidate here too, someone could have called refresh too soon, causing the glyph to get created in the wrong place + } + } + return dag; + } + else + { + // the list is now empty... remove the panel and glyph for this control + RemoveActionGlyph(comp); + return null; + } + + } + + /// + /// We monitor this event so we can update smart tag locations when controls move. + /// + private void OnComponentChanged(object source, ComponentChangedEventArgs ce) + { + if (ce.Component == null || ce.Member == null || !IsDesignerActionPanelVisible) + { + return; + } + + // If the smart tag is showing, we only move the smart tag if the changing component is the component for the currently showing smart tag. + if (_lastPanelComponent != null && !_lastPanelComponent.Equals(ce.Component)) + { + return; + } + + // if something changed on a component we have actions associated with then invalidate all (repaint & reposition) + if (_componentToGlyph[ce.Component] is DesignerActionGlyph glyph) + { + glyph.Invalidate(); + + if (ce.Member.Name.Equals("Dock")) + { // this is the only case were we don't require an explicit refresh + RecreatePanel(ce.Component as IComponent); // because 99% of the time the action is name "dock in parent container" and get replaced by "undock" + } + + if (ce.Member.Name.Equals("Location") || + ce.Member.Name.Equals("Width") || + ce.Member.Name.Equals("Height")) + { + // we don't need to regen, we just need to update location calculate the position of the form hosting the panel + UpdateDAPLocation(ce.Component as IComponent, glyph); + } + } + } + + private void RecreatePanel(IComponent comp) + { + if (_inTransaction || comp != _selSvc.PrimarySelection) + { //we only ever need to do that when the comp is the primary selection + return; + } + // we check wether or not we're in a transaction, if we are, we only the refresh at the end of the transaction to avoid flicker. + if (_serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host) + { + bool hostIsClosingTransaction = false; + if (host is IDesignerHostTransactionState hostTransactionState) + { + hostIsClosingTransaction = hostTransactionState.IsClosingTransaction; + } + if (host.InTransaction && !hostIsClosingTransaction) + { + //Debug.WriteLine("In transaction, bail, but first hookup to the end of the transaction..."); + host.TransactionClosed += new DesignerTransactionCloseEventHandler(DesignerTransactionClosed); + _inTransaction = true; + _relatedComponentTransaction = comp; + return; + } + } + RecreateInternal(comp); + } + + private void DesignerTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) + { + if (e.LastTransaction && _relatedComponentTransaction != null) + { + // surprise surprise we can get multiple even with e.LastTransaction set to true, even though we unhook here this is because the list on which we enumerate (the event handler list) is copied before it's enumerated on which means that if the undo engine for example creates and commit a transaction during the OnCancel of another completed transaction we will get this twice. So we have to check also for relatedComponentTransaction != null + _inTransaction = false; + IDesignerHost host = _serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost; + host.TransactionClosed -= new DesignerTransactionCloseEventHandler(DesignerTransactionClosed); + RecreateInternal(_relatedComponentTransaction); + _relatedComponentTransaction = null; + } + } + + private void RecreateInternal(IComponent comp) + { + //Debug.WriteLine("not in a transaction, do it now!"); + DesignerActionGlyph glyph = GetDesignerActionGlyph(comp); + if (glyph != null) + { + VerifyGlyphIsInAdorner(glyph); // this could happen when a verb change state or suddendly a control gets a new action in the panel and we are the primary selection in that case there would not be a glyph active in the adorner to be shown because we update that on selection change. We have to do that here too. Sad really... + RecreatePanel(glyph); // recreate the DAP itself + UpdateDAPLocation(comp, glyph); // reposition the thing + } + } + private void RecreatePanel(Glyph glyphWithPanelToRegen) + { + // we don't want to do anything if the panel is not visible + if (!IsDesignerActionPanelVisible) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.RecreatePanel] panel is not visible, bail"); + return; + } + //recreate a designeraction panel + if (glyphWithPanelToRegen != null) + { + if (glyphWithPanelToRegen.Behavior is DesignerActionBehavior behaviorWithPanelToRegen) + { + Debug.Assert(behaviorWithPanelToRegen.RelatedComponent != null, "could not find related component for this refresh"); + DesignerActionPanel dap = _designerActionHost.CurrentPanel; // WE DO NOT RECREATE THE WHOLE THING / WE UPDATE THE TASKS - should flicker less + dap.UpdateTasks(behaviorWithPanelToRegen.ActionLists, new DesignerActionListCollection(), string.Format(SR.DesignerActionPanel_DefaultPanelTitle, + behaviorWithPanelToRegen.RelatedComponent.GetType().Name), null); + _designerActionHost.UpdateContainerSize(); + } + } + } + + private void VerifyGlyphIsInAdorner(DesignerActionGlyph glyph) + { + if (glyph.IsInComponentTray) + { + ComponentTray compTray = _serviceProvider.GetService(typeof(ComponentTray)) as ComponentTray; + if (compTray.SelectionGlyphs != null && !compTray.SelectionGlyphs.Contains(glyph)) + { + compTray.SelectionGlyphs.Insert(0, glyph); + } + } + else + { + if (_designerActionAdorner != null && _designerActionAdorner.Glyphs != null && !_designerActionAdorner.Glyphs.Contains(glyph)) + { + _designerActionAdorner.Glyphs.Insert(0, glyph); + } + } + glyph.InvalidateOwnerLocation(); + } + + /// + /// This event is fired by the DesignerActionService in response to a DesignerActionCollection changing. The event args contains information about the related object, the type of change (added or removed) and the remaining DesignerActionCollection for the object. + /// Note that when new DesignerActions are added, if the related control is not yet parented - we add these actions to a "delay" list and they are later created when the control is finally parented. + /// + private void OnDesignerActionsChanged(object sender, DesignerActionListsChangedEventArgs e) + { + // We need to invoke this async because the designer action service will raise this event from the thread pool. + if (_marshalingControl != null && _marshalingControl.IsHandleCreated) + { + _marshalingControl.BeginInvoke(new ActionChangedEventHandler(OnInvokedDesignerActionChanged), new object[] { sender, e }); + } + } + + private void OnDesignerActionUIStateChange(object sender, DesignerActionUIStateChangeEventArgs e) + { + IComponent comp = e.RelatedObject as IComponent; + Debug.Assert(comp != null || e.ChangeType == DesignerActionUIStateChangeType.Hide, "related object is not an IComponent, something is wrong here..."); + if (comp != null) + { + DesignerActionGlyph relatedGlyph = GetDesignerActionGlyph(comp); + if (relatedGlyph != null) + { + if (e.ChangeType == DesignerActionUIStateChangeType.Show) + { + if (relatedGlyph.Behavior is DesignerActionBehavior behavior) + { + behavior.ShowUI(relatedGlyph); + } + } + else if (e.ChangeType == DesignerActionUIStateChangeType.Hide) + { + if (relatedGlyph.Behavior is DesignerActionBehavior behavior) + { + behavior.HideUI(); + } + } + else if (e.ChangeType == DesignerActionUIStateChangeType.Refresh) + { + relatedGlyph.Invalidate(); + RecreatePanel((IComponent)e.RelatedObject); + } + } + } + else + { + if (e.ChangeType == DesignerActionUIStateChangeType.Hide) + { + HideDesignerActionPanel(); + } + } + } + + /// + /// This is the same as DesignerActionChanged, but it is invoked on our control's thread + /// + private void OnInvokedDesignerActionChanged(object sender, DesignerActionListsChangedEventArgs e) + { + DesignerActionGlyph g = null; + IComponent relatedComponent = e.RelatedObject as IComponent; + if (e.ChangeType == DesignerActionListsChangedType.ActionListsAdded) + { + if (relatedComponent == null) + { + Debug.Fail("How can we add a DesignerAction glyphs when it's related object is not an IComponent?"); + return; + } + + IComponent primSel = _selSvc.PrimarySelection as IComponent; + if (primSel == e.RelatedObject) + { + g = GetDesignerActionGlyph(relatedComponent, e.ActionLists); + if (g != null) + { + VerifyGlyphIsInAdorner(g); + } + else + { + RemoveActionGlyph(e.RelatedObject); + } + } + } + + if (e.ChangeType == DesignerActionListsChangedType.ActionListsRemoved && e.ActionLists.Count == 0) + { + // only remove our glyph if there are no more DesignerActions associated with it. + RemoveActionGlyph(e.RelatedObject); + } + else if (g != null) + { + // we need to recreate the panel here, since it's content has changed... + RecreatePanel(relatedComponent); + } + } + + /// + /// Called when our KeyShowDesignerActions menu command is fired (a.k.a. Alt+Shift+F10) - we will find the primary selection, see if it has designer actions, and if so - show the menu. + /// + private void OnKeyShowDesignerActions(object sender, EventArgs e) + { + ShowDesignerActionPanelForPrimarySelection(); + } + + // we cannot attach several menu command to the same command id, we need a single entry point, we put it in designershortcutui. but we need a way to call the show ui on the related behavior hence this internal function to hack it together we return false if we have nothing to display, we hide it and return true if we're already displaying + internal bool ShowDesignerActionPanelForPrimarySelection() + { + if (_selSvc == null) + { + return false; + } + + object primarySelection = _selSvc.PrimarySelection; + + //verfiy that we have obtained a valid component with designer actions + if (primarySelection == null || !_componentToGlyph.Contains(primarySelection)) + { + return false; + } + + DesignerActionGlyph glyph = (DesignerActionGlyph)_componentToGlyph[primarySelection]; + if (glyph != null && glyph.Behavior is DesignerActionBehavior) + { + if (glyph.Behavior is DesignerActionBehavior behavior) + { + if (!IsDesignerActionPanelVisible) + { + behavior.ShowUI(glyph); + return true; + } + else + { + behavior.HideUI(); + return false; + } + } + } + return false; + } + + /// + /// When all the DesignerActions have been removed for a particular object, we remove any UI (glyphs) that we may have been managing. + /// + internal void RemoveActionGlyph(object relatedObject) + { + if (relatedObject == null) + { + return; + } + + if (IsDesignerActionPanelVisible && relatedObject == _lastPanelComponent) + { + HideDesignerActionPanel(); + } + + DesignerActionGlyph glyph = (DesignerActionGlyph)_componentToGlyph[relatedObject]; + if (glyph != null) + { + + // Check ComponentTray first + if (_serviceProvider.GetService(typeof(ComponentTray)) is ComponentTray compTray && compTray.SelectionGlyphs != null) + { + if (compTray != null && compTray.SelectionGlyphs.Contains(glyph)) + { + compTray.SelectionGlyphs.Remove(glyph); + } + } + + if (_designerActionAdorner.Glyphs.Contains(glyph)) + { + _designerActionAdorner.Glyphs.Remove(glyph); + } + _componentToGlyph.Remove(relatedObject); + + // we only do this when we're in a transaction. This is for compat reason - infragistic. if we're not in a transaction, too bad, we don't update the screen + if (_serviceProvider.GetService(typeof(IDesignerHost)) is IDesignerHost host && host.InTransaction) + { + host.TransactionClosed += new DesignerTransactionCloseEventHandler(InvalidateGlyphOnLastTransaction); + _relatedGlyphTransaction = glyph; + } + } + } + + private void InvalidateGlyphOnLastTransaction(object sender, DesignerTransactionCloseEventArgs e) + { + if (e.LastTransaction) + { + IDesignerHost host = (_serviceProvider != null) ? _serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost : null; + if (host != null) + { + host.TransactionClosed -= new DesignerTransactionCloseEventHandler(InvalidateGlyphOnLastTransaction); + } + + if (_relatedGlyphTransaction != null) + { + _relatedGlyphTransaction.InvalidateOwnerLocation(); + } + _relatedGlyphTransaction = null; + } + } + + internal void HideDesignerActionPanel() + { + if (IsDesignerActionPanelVisible) + { + _designerActionHost.Close(); + } + } + + internal bool IsDesignerActionPanelVisible + { + get + { + return (_designerActionHost != null && _designerActionHost.Visible); + } + } + + internal IComponent LastPanelComponent + { + get + { + return (IsDesignerActionPanelVisible ? _lastPanelComponent : null); + } + } + + private void ToolStripDropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e) + { + if (_cancelClose || e.Cancel) + { + e.Cancel = true; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] cancelClose true, bail"); + return; + } + if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) + { + e.Cancel = true; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] ItemClicked: e.Cancel set to: " + e.Cancel.ToString()); + } + if (e.CloseReason == ToolStripDropDownCloseReason.Keyboard) + { + e.Cancel = false; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] Keyboard: e.Cancel set to: " + e.Cancel.ToString()); + } + + if (e.Cancel == false) + { // we WILL disappear + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI.toolStripDropDown_Closing] Closing..."); + Debug.Assert(_lastPanelComponent != null, "last panel component should not be null here... " + + "(except if you're currently debugging VS where deactivation messages in the middle of the pump can mess up everything...)"); + if (_lastPanelComponent == null) + return; + // if we're actually closing get the coordinate of the last message, the one causing us to close, is it within the glyph coordinate + // if it is that mean that someone just clicked back from the panel, on VS, but ON THE GLYPH, that means that he actually wants to close it. The activation change is going to do that for us but we should NOT reopen right away because he clicked on the glyph... this code is here to prevent this... + Point point = DesignerUtils.LastCursorPoint; + if (_componentToGlyph[_lastPanelComponent] is DesignerActionGlyph currentGlyph) + { + Point glyphCoord = GetGlyphLocationScreenCoord(_lastPanelComponent, currentGlyph); + if ((new Rectangle(glyphCoord, new Size(currentGlyph.Bounds.Width, currentGlyph.Bounds.Height))).Contains(point)) + { + DesignerActionBehavior behavior = currentGlyph.Behavior as DesignerActionBehavior; + behavior.IgnoreNextMouseUp = true; + } + currentGlyph.InvalidateOwnerLocation(); + } + + // unset the ownership relationship + _lastPanelComponent = null; + + // panel is going away, pop the behavior that's on the stack... + Debug.Assert(_dapkb != null, "why is dapkb null?"); + System.Windows.Forms.Design.Behavior.Behavior popBehavior = _behaviorService.PopBehavior(_dapkb); + Debug.Assert(popBehavior is DesignerActionKeyboardBehavior, "behavior returned is of the wrong kind?"); + } + } + + internal Point UpdateDAPLocation(IComponent component, DesignerActionGlyph glyph) + { + if (component == null) + { // in case of a resize... + component = _lastPanelComponent; + } + + if (_designerActionHost == null) + { + return Point.Empty; + } + + if (component == null || glyph == null) + { + return _designerActionHost.Location; + } + + // check that the glyph is still visible in the adorner window + if (_behaviorService != null && + !_behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds)) + { + HideDesignerActionPanel(); + return _designerActionHost.Location; + } + + Point glyphLocationScreenCoord = GetGlyphLocationScreenCoord(component, glyph); + Rectangle rectGlyph = new Rectangle(glyphLocationScreenCoord, glyph.Bounds.Size); + Point pt = DesignerActionPanel.ComputePreferredDesktopLocation(rectGlyph, _designerActionHost.Size, out DockStyle edgeToDock); + glyph.DockEdge = edgeToDock; + _designerActionHost.Location = pt; + return pt; + } + + private Point GetGlyphLocationScreenCoord(IComponent relatedComponent, Glyph glyph) + { + Point glyphLocationScreenCoord = new Point(0, 0); + if (relatedComponent is Control && !(relatedComponent is ToolStripDropDown)) + { + glyphLocationScreenCoord = _behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location); + } + // ISSUE: we can't have this special cased here - we should find a more generic approach to solving this problem + else if (relatedComponent is ToolStripItem) + { + if (relatedComponent is ToolStripItem item && item.Owner != null) + { + glyphLocationScreenCoord = _behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location); + } + } + else if (relatedComponent is IComponent) + { + if (_serviceProvider.GetService(typeof(ComponentTray)) is ComponentTray compTray) + { + glyphLocationScreenCoord = compTray.PointToScreen(glyph.Bounds.Location); + } + } + return glyphLocationScreenCoord; + } + + bool _cancelClose = false; + + /// + /// This shows the actual chrome paenl that is created by the DesignerActionBehavior object. + /// + internal void ShowDesignerActionPanel(IComponent relatedComponent, DesignerActionPanel panel, DesignerActionGlyph glyph) + { + if (_designerActionHost == null) + { + _designerActionHost = new DesignerActionToolStripDropDown(this, _mainParentWindow) + { + AutoSize = false, + Padding = Padding.Empty, + Renderer = new NoBorderRenderer(), + Text = "DesignerActionTopLevelForm" + }; + _designerActionHost.Closing += new ToolStripDropDownClosingEventHandler(ToolStripDropDown_Closing); + + + } + // set the accessible name of the panel to the same title as the panel header. do that every time + _designerActionHost.AccessibleName = string.Format(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name); + panel.AccessibleName = string.Format(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name); + + _designerActionHost.SetDesignerActionPanel(panel, glyph); + Point location = UpdateDAPLocation(relatedComponent, glyph); + + // check that the panel will have at least it's parent glyph visible on the adorner window + if (_behaviorService != null && + _behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds)) + { + if (_mainParentWindow != null && _mainParentWindow.Handle != IntPtr.Zero) + { + Debug.WriteLineIf(s_designeActionPanelTraceSwitch.TraceVerbose, "Assigning owner to mainParentWindow"); + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "Assigning owner to mainParentWindow"); + UnsafeNativeMethods.SetWindowLong(new HandleRef(_designerActionHost, _designerActionHost.Handle), + NativeMethods.GWL_HWNDPARENT, + new HandleRef(_mainParentWindow, _mainParentWindow.Handle)); + } + + _cancelClose = true; + + _designerActionHost.Show(location); + _designerActionHost.Focus(); + // when a control is drag and dropped and authoshow is set to true the vs designer is going to get activated as soon as the control is dropped we don't want to close the panel then, so we post a message (using the trick to call begin invoke) and once everything is settled re-activate the autoclose logic + _designerActionHost.BeginInvoke(new EventHandler(OnShowComplete)); + + // invalidate the glyph to have it point the other way + glyph.InvalidateOwnerLocation(); + _lastPanelComponent = relatedComponent; + + // push new behavior for keyboard handling on the behavior stack + _dapkb = new DesignerActionKeyboardBehavior(_designerActionHost.CurrentPanel, _serviceProvider, _behaviorService); + _behaviorService.PushBehavior(_dapkb); + } + } + + private void OnShowComplete(object sender, EventArgs e) + { + _cancelClose = false; + + // force the panel to be the active window - for some reason someone else could have forced VS to become active for real while we were ignoring close. This might be bad cause we'd be in a bad state. + if (_designerActionHost != null && _designerActionHost.Handle != IntPtr.Zero && _designerActionHost.Visible) + { + UnsafeNativeMethods.SetActiveWindow(new HandleRef(this, _designerActionHost.Handle)); + _designerActionHost.CheckFocusIsRight(); + } + } + } + + internal class DesignerActionToolStripDropDown : ToolStripDropDown + { + private readonly IWin32Window _mainParentWindow; + private ToolStripControlHost _panel; + private readonly DesignerActionUI _designerActionUI; + private bool _cancelClose = false; + + private Glyph _relatedGlyph; + + public DesignerActionToolStripDropDown(DesignerActionUI designerActionUI, IWin32Window mainParentWindow) + { + _mainParentWindow = mainParentWindow; + _designerActionUI = designerActionUI; + } + + + public DesignerActionPanel CurrentPanel + { + get + { + if (_panel != null) + { + return _panel.Control as DesignerActionPanel; + } + else + { + return null; + } + } + } + + // we're not topmost because we can show modal editors above us. + protected override bool TopMost + { + get { return false; } + } + + public void UpdateContainerSize() + { + if (CurrentPanel != null) + { + Size panelSize = CurrentPanel.GetPreferredSize(new Size(150, int.MaxValue)); + if (CurrentPanel.Size == panelSize) + { + // If the panel size didn't actually change, we still have to force a call to PerformLayout to make sure that controls get repositioned properly within the panel. The issue arises because we did a measure-only Layout that determined some sizes, and then we end up painting with those values even though there wasn't an actual Layout performed. + CurrentPanel.PerformLayout(); + } + else + { + CurrentPanel.Size = panelSize; + } + ClientSize = panelSize; + } + } + + public void CheckFocusIsRight() + { // hack to get the focus to NOT stay on ContainerControl + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "Checking focus..."); + IntPtr focusedControl = UnsafeNativeMethods.GetFocus(); + if (focusedControl == Handle) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, " putting focus on the panel..."); + _panel.Focus(); + } + focusedControl = UnsafeNativeMethods.GetFocus(); + if (CurrentPanel != null && CurrentPanel.Handle == focusedControl) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, " selecting next available control on the panel..."); + CurrentPanel.SelectNextControl(null, true, true, true, true); + } + _ = UnsafeNativeMethods.GetFocus(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout(levent); + + UpdateContainerSize(); + } + + protected override void OnClosing(ToolStripDropDownClosingEventArgs e) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "_____________________________Begin OnClose " + e.CloseReason.ToString()); + Debug.Indent(); + if (e.CloseReason == ToolStripDropDownCloseReason.AppFocusChange && _cancelClose) + { + _cancelClose = false; + e.Cancel = true; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "cancel close prepopulated"); + } + // when we get closing event as a result of an activation change, pre-populate e.Cancel based on why we're exiting. + // - if it's a modal window that's owned by VS dont exit + // - if it's a window that's owned by the toolstrip dropdown dont exit + + else if (e.CloseReason == ToolStripDropDownCloseReason.AppFocusChange || e.CloseReason == ToolStripDropDownCloseReason.AppClicked) + { + IntPtr hwndActivating = UnsafeNativeMethods.GetActiveWindow(); + if (Handle == hwndActivating && e.CloseReason == ToolStripDropDownCloseReason.AppClicked) + { + e.Cancel = false; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] activation hasnt changed, but we've certainly clicked somewhere else."); + } + else if (WindowOwnsWindow(Handle, hwndActivating)) + { + // we're being de-activated for someone owned by the panel + e.Cancel = true; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] Cancel close - the window activating is owned by this window"); + } + else if (_mainParentWindow != null && !WindowOwnsWindow(_mainParentWindow.Handle, hwndActivating)) + { + if (IsWindowEnabled(_mainParentWindow.Handle)) + { + // the activated windows is not a child/owned windows of the main top level windows let toolstripdropdown handle this + e.Cancel = false; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] Call close: the activated windows is not a child/owned windows of the main top level windows "); + } + else + { + e.Cancel = true; + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] we're being deactivated by a foreign window, but the main window is not enabled - we should stay up"); + } + + base.OnClosing(e); + Debug.Unindent(); + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "_____________________________End OnClose e.Cancel: " + e.Cancel.ToString()); + return; + } + else + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] since the designer action panel dropdown doesnt own the activating window " + hwndActivating.ToString("x") + ", calling close. "); + } + + + // what's the owner of the windows being activated? + IntPtr parent = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, hwndActivating), NativeMethods.GWL_HWNDPARENT); + // is it currently disabled (ie, the activating windows is in modal mode) + if (!IsWindowEnabled(parent)) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] modal window activated - cancelling close"); + // we are in a modal case + e.Cancel = true; + } + } + else + { + } + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.OnClosing] calling base.OnClosing with e.Cancel: " + e.Cancel.ToString()); + + base.OnClosing(e); + Debug.Unindent(); + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "_____________________________End OnClose e.Cancel: " + e.Cancel.ToString()); + + } + + public void SetDesignerActionPanel(DesignerActionPanel panel, Glyph relatedGlyph) + { + if (_panel != null && panel == (DesignerActionPanel)_panel.Control) + return; + + Debug.Assert(relatedGlyph != null, "related glyph cannot be null"); + + _relatedGlyph = relatedGlyph; + + panel.SizeChanged += new EventHandler(PanelResized); + // hook up the event + if (_panel != null) + { + Items.Remove(_panel); + _panel.Dispose(); + _panel = null; + } + _panel = new ToolStripControlHost(panel) + { + // we don't want no margin + Margin = Padding.Empty, + Size = panel.Size + }; + + SuspendLayout(); + Size = panel.Size; + Items.Add(_panel); + ResumeLayout(); + + if (Visible) + { + CheckFocusIsRight(); + } + + } + + private void PanelResized(object sender, EventArgs e) + { + Control ctrl = sender as Control; + if (Size.Width != ctrl.Size.Width || Size.Height != ctrl.Size.Height) + { + SuspendLayout(); + Size = ctrl.Size; + if (_panel != null) + { + _panel.Size = ctrl.Size; + } + _designerActionUI.UpdateDAPLocation(null, _relatedGlyph as DesignerActionGlyph); + ResumeLayout(); + } + } + + protected override void SetVisibleCore(bool visible) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionToolStripDropDown.SetVisibleCore] setting dropdown visible=" + visible.ToString()); + base.SetVisibleCore(visible); + if (visible) + { + CheckFocusIsRight(); + } + } + + /// + /// General purpose method, based on Control.Contains()... + /// Determines whether a given window (specified using native window handle) is a descendant of this control. This catches both contained descendants and 'owned' windows such as modal dialogs. Using window handles rather than Control objects allows it to catch un-managed windows as well. + /// + private static bool WindowOwnsWindow(IntPtr hWndOwner, IntPtr hWndDescendant) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[WindowOwnsWindow] Testing if " + hWndOwner.ToString("x") + " is a owned by " + hWndDescendant.ToString("x") + "... "); +#if DEBUG + if (DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose) + { + Debug.WriteLine("\t\tOWNER: " + GetControlInformation(hWndOwner)); + Debug.WriteLine("\t\tOWNEE: " + GetControlInformation(hWndDescendant)); + + IntPtr claimedOwnerHwnd = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hWndDescendant), NativeMethods.GWL_HWNDPARENT); + Debug.WriteLine("OWNEE's CLAIMED OWNER: " + GetControlInformation(claimedOwnerHwnd)); + } + +#endif + if (hWndDescendant == hWndOwner) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "they match, YES."); + return true; + } + + while (hWndDescendant != IntPtr.Zero) + { + hWndDescendant = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hWndDescendant), NativeMethods.GWL_HWNDPARENT); + if (hWndDescendant == IntPtr.Zero) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "NOPE."); + return false; + } + if (hWndDescendant == hWndOwner) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "YES."); + return true; + } + } + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "NO."); + return false; + } + + // helper function for generating infomation about a particular control use AssertControlInformation if sticking in an assert - then the work to figure out the control info will only be done when the assertion is false. + internal static string GetControlInformation(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) + { + return "Handle is IntPtr.Zero"; + } +#if DEBUG + if (!DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose) + { + return string.Empty; + } + + int textLen = SafeNativeMethods.GetWindowTextLength(new HandleRef(null, hwnd)); + StringBuilder sb = new StringBuilder(textLen + 1); + UnsafeNativeMethods.GetWindowText(new HandleRef(null, hwnd), sb, sb.Capacity); + + string typeOfControl = "Unknown"; + string nameOfControl = ""; + Control c = Control.FromHandle(hwnd); + if (c != null) + { + typeOfControl = c.GetType().Name; + if (!string.IsNullOrEmpty(c.Name)) + { + nameOfControl += c.Name; + } + else + { + nameOfControl += "Unknown"; + // some extra debug info for toolstripdropdowns... + if (c is ToolStripDropDown dd) + { + if (dd.OwnerItem != null) + { + nameOfControl += "OwnerItem: [" + dd.OwnerItem.ToString() + "]"; + } + } + } + } + return sb.ToString() + "\r\n\t\t\tType: [" + typeOfControl + "] Name: [" + nameOfControl + "]"; +#else + return String.Empty; +#endif + + } + private bool IsWindowEnabled(IntPtr handle) + { + int style = (int)UnsafeNativeMethods.GetWindowLong(new HandleRef(this, handle), NativeMethods.GWL_STYLE); + return (style & NativeMethods.WS_DISABLED) == 0; + } + + private void WmActivate(ref Message m) + { + if (unchecked((int)(long)m.WParam) == NativeMethods.WA_INACTIVE) + { + IntPtr hwndActivating = m.LParam; + if (WindowOwnsWindow(Handle, hwndActivating)) + { + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI WmActivate] setting cancel close true because WindowsOwnWindow"); + + Debug.WriteLineIf(DesignerActionUI.s_dropDownVisibilityDebug.TraceVerbose, "[DesignerActionUI WmActivate] checking the focus... " + GetControlInformation(UnsafeNativeMethods.GetFocus())); + + _cancelClose = true; + } + else + { + _cancelClose = false; + } + } + else + { + _cancelClose = false; + } + + base.WndProc(ref m); + } + + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + case NativeMethods.WM_ACTIVATE: + WmActivate(ref m); + return; + } + base.WndProc(ref m); + } + + protected override bool ProcessDialogKey(Keys keyData) + { + // since we're not hosted in a form we need to do the same logic as Form.cs. If we get an enter key we need to find the current focused control if it's a button, we click it and return that we handled the message + if (keyData == Keys.Enter) + { + IntPtr focusedControlPtr = UnsafeNativeMethods.GetFocus(); + Control focusedControl = Control.FromChildHandle(focusedControlPtr); + if (focusedControl is IButtonControl button && button is Control) + { + button.PerformClick(); + return true; + } + } + return base.ProcessDialogKey(keyData); + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIService.cs new file mode 100644 index 00000000000..5ab88d536e6 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIService.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Windows.Forms.Design +{ + public sealed class DesignerActionUIService : IDisposable + { + private DesignerActionUIStateChangeEventHandler _designerActionUIStateChangedEventHandler; + private readonly IServiceProvider _serviceProvider; //standard service provider + private readonly DesignerActionService _designerActionService; + + internal DesignerActionUIService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + + if (serviceProvider != null) + { + _serviceProvider = serviceProvider; + + IDesignerHost host = (IDesignerHost)serviceProvider.GetService(typeof(IDesignerHost)); + host.AddService(typeof(DesignerActionUIService), this); + + _designerActionService = serviceProvider.GetService(typeof(DesignerActionService)) as DesignerActionService; + Debug.Assert(_designerActionService != null, "we should have created and registered the DAService first"); + } + } + + /// + /// Disposes all resources and unhooks all events. + /// + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")] + public void Dispose() + { + if (_serviceProvider != null) + { + IDesignerHost host = (IDesignerHost)_serviceProvider.GetService(typeof(IDesignerHost)); + if (host != null) + { + host.RemoveService(typeof(DesignerActionUIService)); + } + } + } + + /// + /// This event is thrown whenever a request is made to show/hide the ui + /// + public event DesignerActionUIStateChangeEventHandler DesignerActionUIStateChange + { + add + { + _designerActionUIStateChangedEventHandler += value; + } + remove + { + _designerActionUIStateChangedEventHandler -= value; + } + } + + + public void HideUI(IComponent component) + { + OnDesignerActionUIStateChange(new DesignerActionUIStateChangeEventArgs(component, DesignerActionUIStateChangeType.Hide)); + } + + public void ShowUI(IComponent component) + { + OnDesignerActionUIStateChange(new DesignerActionUIStateChangeEventArgs(component, DesignerActionUIStateChangeType.Show)); + } + + /// + /// This is a new Helper Method that the service provides to refresh the DesignerActionGlyph as well as DesignerActionPanels. + /// + public void Refresh(IComponent component) + { + OnDesignerActionUIStateChange(new DesignerActionUIStateChangeEventArgs(component, DesignerActionUIStateChangeType.Refresh)); + } + + /// + /// This fires our DesignerActionsChanged event. + /// + private void OnDesignerActionUIStateChange(DesignerActionUIStateChangeEventArgs e) + { + _designerActionUIStateChangedEventHandler?.Invoke(this, e); + } + + public bool ShouldAutoShow(IComponent component) + { + if (_serviceProvider != null) + { + if (_serviceProvider.GetService(typeof(DesignerOptionService)) is DesignerOptionService opts) + { + PropertyDescriptor p = opts.Options.Properties["ObjectBoundSmartTagAutoShow"]; + if (p != null && p.PropertyType == typeof(bool) && !(bool)p.GetValue(null)) + { + return false; + } + } + } + + if (_designerActionService != null) + { + DesignerActionListCollection coll = _designerActionService.GetComponentActions(component); + if (coll != null && coll.Count > 0) + { + for (int i = 0; i < coll.Count; i++) + { + if (coll[i].AutoShow) + { + return true; + } + } + } + } + return false; + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventArgs.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventArgs.cs new file mode 100644 index 00000000000..a307989ce40 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventArgs.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + /// + /// This EventArgs class is used by the DesignerActionService to signify that there has been a change in DesignerActionLists (added or removed) on the related object. + /// + public class DesignerActionUIStateChangeEventArgs : EventArgs + { + private readonly object _relatedObject; + private readonly DesignerActionUIStateChangeType _changeType; + + /// + /// Constructor that requires the object in question, the type of change and the remaining actionlists left for the object on the related object. + /// + public DesignerActionUIStateChangeEventArgs(object relatedObject, DesignerActionUIStateChangeType changeType) + { + _relatedObject = relatedObject; + _changeType = changeType; + } + + /// + /// The type of changed that caused the related event to be thrown. + /// + public DesignerActionUIStateChangeType ChangeType => _changeType; + + /// + /// The object this change is related to. + /// + public object RelatedObject => _relatedObject; + + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventHandler.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventHandler.cs new file mode 100644 index 00000000000..48b12fe08b6 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeEventHandler.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + public delegate void DesignerActionUIStateChangeEventHandler(object sender, DesignerActionUIStateChangeEventArgs e); +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeType.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeType.cs new file mode 100644 index 00000000000..ed9edc4cec5 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionUIStateChangeType.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + public enum DesignerActionUIStateChangeType + { + Show, Hide, Refresh + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbItem.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbItem.cs new file mode 100644 index 00000000000..db24cc17e38 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbItem.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + internal class DesignerActionVerbItem : DesignerActionMethodItem + { + private readonly DesignerVerb _targetVerb; + + public DesignerActionVerbItem(DesignerVerb verb) + { + _targetVerb = verb ?? throw new ArgumentNullException(); + } + + public override string Category + { + get => "Verbs"; + } + + public override string Description { get; } + + public override string DisplayName + { + get => _targetVerb.Text; + } + + public override string MemberName + { + get => null; + } + + public override bool IncludeAsDesignerVerb + { + get => false; + } + + public override void Invoke() + { + _targetVerb.Invoke(); + } + + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbList.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbList.cs new file mode 100644 index 00000000000..6ef7876dfe0 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerActionVerbList.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Design +{ + internal class DesignerActionVerbList : DesignerActionList + { + private readonly DesignerVerb[] _verbs; + + public DesignerActionVerbList(DesignerVerb[] verbs) : base(null) + { + _verbs = verbs; + } + + public override bool AutoShow + { + get => false; + } + + public override DesignerActionItemCollection GetSortedActionItems() + { + DesignerActionItemCollection items = new DesignerActionItemCollection(); + for (int i = 0; i < _verbs.Length; i++) + { + if (_verbs[i].Visible && _verbs[i].Enabled && _verbs[i].Supported) + { + items.Add(new DesignerActionVerbItem(_verbs[i])); + } + } + return items; + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerCommandSet.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerCommandSet.cs new file mode 100644 index 00000000000..3358d4358c8 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerCommandSet.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel.Design; + +namespace System.Windows.Forms.Design +{ + public class DesignerCommandSet + { + public virtual ICollection GetCommands(string name) + { + return null; + } + + public DesignerVerbCollection Verbs + { + get => (DesignerVerbCollection)GetCommands("Verbs"); + } + + public DesignerActionListCollection ActionLists + { + get => (DesignerActionListCollection)GetCommands("ActionLists"); + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerFrame.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerFrame.cs new file mode 100644 index 00000000000..3bb73cc2163 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerFrame.cs @@ -0,0 +1,641 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms.Design.Behavior; +using Microsoft.Win32; + +namespace System.Windows.Forms.Design +{ + /// + /// This class implements our design time document. This is the outer window that encompases a designer. It maintains a control hierarchy that looks like this: + /// DesignerFrame + /// ScrollableControl + /// Designer + /// Splitter + /// ScrollableControl + /// Component Tray + /// The splitter and second scrollable control are created on demand when a tray is added. + /// + internal class DesignerFrame : Control, IOverlayService, ISplitWindowService, IContainsThemedScrollbarWindows + { + private readonly ISite _designerSite; + private readonly OverlayControl _designerRegion; + private Splitter _splitter; + private Control _designer; + private BehaviorService _behaviorService; + private readonly IUIService _uiService; + + /// + /// Initializes a new instance of the class. + /// + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + public DesignerFrame(ISite site) + { + + Text = "DesignerFrame"; + _designerSite = site; + _designerRegion = new OverlayControl(site); + + _uiService = _designerSite.GetService(typeof(IUIService)) as IUIService; + if (_uiService != null) + { + if (_uiService.Styles["ArtboardBackground"] is Color) + { + BackColor = (Color)_uiService.Styles["ArtboardBackground"]; + } + } + Controls.Add(_designerRegion); + + // Now we must configure our designer to be at the correct location, and setup the autoscrolling for its container. + _designerRegion.AutoScroll = true; + _designerRegion.Dock = DockStyle.Fill; + } + + /// + /// Returns the scroll offset for the scrollable control that manages all overlays. This is needed by the BehaviorService so we can correctly invalidate our AdornerWindow based on scrollposition. + /// + internal Point AutoScrollPosition + { + get => _designerRegion.AutoScrollPosition; + } + + /// + /// Demand creates a ptr to the BehaviorService - we do this so we can route keyboard message to it. + /// + private BehaviorService BehaviorService + { + get + { + if (_behaviorService == null) + { + _behaviorService = _designerSite.GetService(typeof(BehaviorService)) as BehaviorService; + } + return _behaviorService; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_designer != null) + { + Control designerHolder = _designer; + _designer = null; + designerHolder.Visible = false; + designerHolder.Parent = null; + SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + if (_splitter != null) + { + _splitter.SplitterMoved -= new SplitterEventHandler(OnSplitterMoved); + } + } + base.Dispose(disposing); + } + + private void ForceDesignerRedraw(bool focus) + { + if (_designer != null && _designer.IsHandleCreated) + { + NativeMethods.SendMessage(_designer.Handle, NativeMethods.WM_NCACTIVATE, focus ? 1 : 0, 0); + SafeNativeMethods.RedrawWindow(_designer.Handle, null, IntPtr.Zero, NativeMethods.RDW_FRAME); + } + } + + /// + /// Initializes this frame with the given designer view. + /// + public void Initialize(Control view) + { + _designer = view; + if (_designer is Form form) + { + form.TopLevel = false; + } + + _designerRegion.Controls.Add(_designer); + SyncDesignerUI(); + _designer.Visible = true; + _designer.Enabled = true; + + // We need to force handle creation here, since setting Visible = true won't if the control is already Visible = true. (UserControl starts out Visible true, Form does not) This guarantees that as controls are added to the root component their handles will be created correctly, and not the first time they're queried after load. + IntPtr handle = _designer.Handle; + // Hook the handler here, when we know that the designer object has already been set + SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + + /// + /// When we get an lose focus, we need to make sure the form designer knows about it so it'll paint it's caption right. + /// + protected override void OnGotFocus(EventArgs e) + { + ForceDesignerRedraw(true); + + ISelectionService selSvc = (ISelectionService)_designerSite.GetService(typeof(ISelectionService)); + if (selSvc != null) + { + if (selSvc.PrimarySelection is Control ctrl && !ctrl.IsDisposed) + { + UnsafeNativeMethods.NotifyWinEvent((int)AccessibleEvents.Focus, new HandleRef(ctrl, ctrl.Handle), NativeMethods.OBJID_CLIENT, 0); + } + } + } + + /// + /// When we get an lose focus, we need to make sure the form designer knows about it so it'll paint it's caption right. + /// + protected override void OnLostFocus(EventArgs e) + { + ForceDesignerRedraw(false); + } + + void OnSplitterMoved(object sender, SplitterEventArgs e) + { + // Dirty the designer. + if (_designerSite.GetService(typeof(IComponentChangeService)) is IComponentChangeService cs) + { + try + { + cs.OnComponentChanging(_designerSite.Component, null); + cs.OnComponentChanged(_designerSite.Component, null, null, null); + } + catch + { + } + } + } + + void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + if (e.Category == UserPreferenceCategory.Window && _designer != null) + { + SyncDesignerUI(); + } + } + + /// + /// We override this to do nothing. Otherwise, all the nice keyboard messages we want would get run through the Form's keyboard handling procedure. + /// + protected override bool ProcessDialogKey(Keys keyData) + { + return false; + } + + void SyncDesignerUI() + { + Size selectionSize = DesignerUtils.GetAdornmentDimensions(AdornmentType.Maximum); + + _designerRegion.AutoScrollMargin = selectionSize; + _designer.Location = new Point(selectionSize.Width, selectionSize.Height); + + if (BehaviorService != null) + { + BehaviorService.SyncSelection(); + } + } + + /// + /// Base wndProc. All messages are sent to wndProc after getting filtered through the preProcessMessage function. Inheriting controls should call base.wndProc for any messages that they don't handle. + /// + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + // Provide MouseWheel access for scrolling + case NativeMethods.WM_MOUSEWHEEL: + // Send a message to ourselves to scroll + if (!_designerRegion._messageMouseWheelProcessed) + { + _designerRegion._messageMouseWheelProcessed = true; + NativeMethods.SendMessage(_designerRegion.Handle, NativeMethods.WM_MOUSEWHEEL, m.WParam, m.LParam); + return; + } + break; + + // Provide keyboard access for scrolling + case NativeMethods.WM_KEYDOWN: + int wScrollNotify = 0; + int msg = 0; + + int keycode = unchecked((int)(long)m.WParam) & 0xFFFF; + switch ((Keys)keycode) + { + case Keys.Up: + wScrollNotify = NativeMethods.SB_LINEUP; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.Down: + wScrollNotify = NativeMethods.SB_LINEDOWN; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.PageUp: + wScrollNotify = NativeMethods.SB_PAGEUP; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.PageDown: + wScrollNotify = NativeMethods.SB_PAGEDOWN; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.Home: + wScrollNotify = NativeMethods.SB_TOP; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.End: + wScrollNotify = NativeMethods.SB_BOTTOM; + msg = NativeMethods.WM_VSCROLL; + break; + case Keys.Left: + wScrollNotify = NativeMethods.SB_LINEUP; + msg = NativeMethods.WM_HSCROLL; + break; + case Keys.Right: + wScrollNotify = NativeMethods.SB_LINEDOWN; + msg = NativeMethods.WM_HSCROLL; + break; + } + if ((msg == NativeMethods.WM_VSCROLL) + || (msg == NativeMethods.WM_HSCROLL)) + { + // Send a message to ourselves to scroll + NativeMethods.SendMessage(_designerRegion.Handle, msg, NativeMethods.Util.MAKELONG(wScrollNotify, 0), 0); + return; + } + break; + case NativeMethods.WM_CONTEXTMENU: + NativeMethods.SendMessage(_designer.Handle, m.Msg, m.WParam, m.LParam); + return; + } + base.WndProc(ref m); + } + + /// + /// Pushes the given control on top of the overlay list. This is a "push" operation, meaning that it forces this control to the top of the existing overlay list. + /// + int IOverlayService.PushOverlay(Control control) + { + return _designerRegion.PushOverlay(control); + } + + /// + /// Removes the given control from the overlay list. Unlike pushOverlay, this can remove a control from the middle of the overlay list. + /// + void IOverlayService.RemoveOverlay(Control control) + { + _designerRegion.RemoveOverlay(control); + } + + /// + /// Inserts the overlay. + /// + void IOverlayService.InsertOverlay(Control control, int index) + { + _designerRegion.InsertOverlay(control, index); + } + + /// + /// Invalidate child overlays + /// + void IOverlayService.InvalidateOverlays(Rectangle screenRectangle) + { + _designerRegion.InvalidateOverlays(screenRectangle); + } + + /// + /// Invalidate child overlays + /// + void IOverlayService.InvalidateOverlays(Region screenRegion) + { + _designerRegion.InvalidateOverlays(screenRegion); + } + + /// + /// Requests the service to add a window 'pane'. + /// + void ISplitWindowService.AddSplitWindow(Control window) + { + if (_splitter == null) + { + _splitter = new Splitter(); + if (_uiService != null && _uiService.Styles["HorizontalResizeGrip"] is Color) + { + _splitter.BackColor = (Color)_uiService.Styles["HorizontalResizeGrip"]; + } + else + { + _splitter.BackColor = SystemColors.Control; + } + _splitter.BorderStyle = BorderStyle.Fixed3D; + _splitter.Height = 7; + _splitter.Dock = DockStyle.Bottom; + _splitter.SplitterMoved += new SplitterEventHandler(OnSplitterMoved); + } + + SuspendLayout(); + window.Dock = DockStyle.Bottom; + + // Compute a minimum height for this window. + int minHeight = 80; + if (window.Height < minHeight) + { + window.Height = minHeight; + } + + Controls.Add(_splitter); + Controls.Add(window); + ResumeLayout(); + } + + /// + /// Requests the service to remove a window 'pane'. + /// + void ISplitWindowService.RemoveSplitWindow(Control window) + { + SuspendLayout(); + Controls.Remove(window); + Controls.Remove(_splitter); + ResumeLayout(); + } + + /// + /// Returns IEnumerable of all windows which need to be themed when running inside VS + /// We don't know how to do theming here but we know which windows need to be themed. + /// The two ScrollableControls that hold the designer and the tray need to be themed, all of the children of the designed form should not be themed. The tray contains only conrols which are not visible in the user app but are visible inside VS. + /// As a result, we want to theme all windows within the tray but only the top window for the designer pane. + /// + IEnumerable IContainsThemedScrollbarWindows.ThemedScrollbarWindows() + { + ArrayList windows = new ArrayList(); + foreach (Control c in Controls) + { + ThemedScrollbarWindow windowInfo = new ThemedScrollbarWindow + { + Handle = c.Handle + }; + if (c is OverlayControl) + { + windowInfo.Mode = ThemedScrollbarMode.OnlyTopLevel; + } + else + { + windowInfo.Mode = ThemedScrollbarMode.All; + } + + windows.Add(windowInfo); + } + + return windows; + } + + /// + /// This is a scrollable control that supports additional floating overlay controls. + /// + private class OverlayControl : ScrollableControl + { + private readonly ArrayList _overlayList; + private IServiceProvider _provider; + internal bool _messageMouseWheelProcessed; + private BehaviorService _behaviorService; + + /// + /// Creates a new overlay control. + /// + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + public OverlayControl(IServiceProvider provider) + { + OverlayControl overlayControl = this; + overlayControl._provider = provider; + _overlayList = new ArrayList(); + AutoScroll = true; + Text = "OverlayControl"; + } + + protected override AccessibleObject CreateAccessibilityInstance() + { + return new OverlayControlAccessibleObject(this); + } + + /// + /// Demand creates a ptr to the BehaviorService + /// + private BehaviorService BehaviorService + { + get + { + if (_behaviorService == null) + { + _behaviorService = _provider.GetService(typeof(BehaviorService)) as BehaviorService; + } + return _behaviorService; + } + } + + /// + /// At handle creation time we request the designer's handle and parent it. + /// + protected override void OnCreateControl() + { + base.OnCreateControl(); + // Loop through all of the overlays, create them, and hook them up + if (_overlayList != null) + { + foreach (Control c in _overlayList) + { + ParentOverlay(c); + } + } + + // We've reparented everything, which means that our selection UI is probably out of sync. Ask it to sync. + if (BehaviorService != null) + { + BehaviorService.SyncSelection(); + } + } + + /// + /// We override onLayout to provide our own custom layout functionality. This just overlaps all of the controls. + /// + protected override void OnLayout(LayoutEventArgs e) + { + base.OnLayout(e); + Rectangle client = DisplayRectangle; + + // Loop through all of the overlays and size them. Also make sure that they are still on top of the zorder, because a handle recreate could have changed this. + if (_overlayList != null) + { + foreach (Control c in _overlayList) + { + c.Bounds = client; + } + } + } + + /// + /// Called to parent an overlay window into our document. This assumes that we call in reverse stack order, as it always pushes to the top of the z-order. + /// + private void ParentOverlay(Control control) + { + NativeMethods.SetParent(control.Handle, Handle); + SafeNativeMethods.SetWindowPos(control.Handle, (IntPtr)NativeMethods.HWND_TOP, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); + } + + /// + /// Pushes the given control on top of the overlay list. This is a "push" operation, meaning that it forces this control to the top of the existing overlay list. + /// + public int PushOverlay(Control control) + { + Debug.Assert(_overlayList.IndexOf(control) == -1, "Duplicate overlay in overlay service :" + control.GetType().FullName); + _overlayList.Add(control); + // We need to have these components parented, but we don't want them to effect our layout. + if (IsHandleCreated) + { + ParentOverlay(control); + control.Bounds = DisplayRectangle; + } + return _overlayList.IndexOf(control); + } + + /// + /// Removes the given control from the overlay list. Unlike pushOverlay, this can remove a control from the middle of the overlay list. + /// + public void RemoveOverlay(Control control) + { + Debug.Assert(_overlayList.IndexOf(control) != -1, "Control is not in overlay service :" + control.GetType().FullName); + _overlayList.Remove(control); + control.Visible = false; + control.Parent = null; + } + + /// + /// Inserts Overlay. + /// + public void InsertOverlay(Control control, int index) + { + Debug.Assert(_overlayList.IndexOf(control) == -1, "Duplicate overlay in overlay service :" + control.GetType().FullName); + Control c = (Control)_overlayList[index]; + RemoveOverlay(c); + PushOverlay(control); + PushOverlay(c); + c.Visible = true; + } + + /// + /// Invalidates overlays that intersect with the given section of the screen; + /// + public void InvalidateOverlays(Rectangle screenRectangle) + { + // paint in inverse order so that things at the front paint last. + for (int i = _overlayList.Count - 1; i >= 0; i--) + { + if (_overlayList[i] is Control overlayControl) + { + Rectangle invalidateRect = new Rectangle(overlayControl.PointToClient(screenRectangle.Location), screenRectangle.Size); + if (overlayControl.ClientRectangle.IntersectsWith(invalidateRect)) + { + overlayControl.Invalidate(invalidateRect); + } + } + } + } + + /// + /// Invalidates overlays that intersect with the given section of the screen; + /// + public void InvalidateOverlays(Region screenRegion) + { + // paint in inverse order so that things at the front paint last. + for (int i = _overlayList.Count - 1; i >= 0; i--) + { + if (_overlayList[i] is Control overlayControl) + { + Rectangle overlayControlScreenBounds = overlayControl.Bounds; + overlayControlScreenBounds.Location = overlayControl.PointToScreen(overlayControl.Location); + using (Region intersectionRegion = screenRegion.Clone()) + { + // get the intersection of everything on the screen that's invalidating + // and the overlaycontrol + intersectionRegion.Intersect(overlayControlScreenBounds); + + // translate this down to overlay control coordinates. + intersectionRegion.Translate(-overlayControlScreenBounds.X, -overlayControlScreenBounds.Y); + overlayControl.Invalidate(intersectionRegion); + } + } + } + } + + /// + /// Need to know when child windows are created so we can properly set the Z-order + /// + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + if (m.Msg == NativeMethods.WM_PARENTNOTIFY && NativeMethods.Util.LOWORD(unchecked((int)(long)m.WParam)) == (short)NativeMethods.WM_CREATE) + { + if (_overlayList != null) + { + bool ourWindow = false; + foreach (Control c in _overlayList) + { + if (c.IsHandleCreated && m.LParam == c.Handle) + { + ourWindow = true; + break; + } + } + + if (!ourWindow) + { + foreach (Control c in _overlayList) + { + SafeNativeMethods.SetWindowPos(c.Handle, (IntPtr)NativeMethods.HWND_TOP, 0, 0, 0, 0, + NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); + } + } + } + } + else if ((m.Msg == NativeMethods.WM_VSCROLL || m.Msg == NativeMethods.WM_HSCROLL) && BehaviorService != null) + { + BehaviorService.SyncSelection(); + } + else if ((m.Msg == NativeMethods.WM_MOUSEWHEEL)) + { + _messageMouseWheelProcessed = false; + if (BehaviorService != null) + { + BehaviorService.SyncSelection(); + } + } + } + + public class OverlayControlAccessibleObject : Control.ControlAccessibleObject + { + public OverlayControlAccessibleObject(OverlayControl owner) : base(owner) + { + } + + public override AccessibleObject HitTest(int x, int y) + { + // Since the SelectionUIOverlay in first in the z-order, it normally gets returned from accHitTest. But we'd rather expose the form that is being designed. + foreach (Control c in Owner.Controls) + { + AccessibleObject cao = c.AccessibilityObject; + if (cao.Bounds.Contains(x, y)) + { + return cao; + } + } + return base.HitTest(x, y); + } + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerOptions.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerOptions.cs index 645b6e9fa01..14ed6688dd0 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerOptions.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerOptions.cs @@ -9,95 +9,115 @@ namespace System.Windows.Forms.Design { /// - /// Provides access to get and set option values for a designer. + /// Provides access to get and set option values for a designer. /// [ComVisible(true)] public class DesignerOptions { + private const int MinGridSize = 2; + private const int MaxGridSize = 200; + private bool _showGrid = true; + private bool _snapToGrid = true; + private Size _gridSize = new Size(8, 8); + + private bool _useSnapLines = false; + private bool _useSmartTags = false; + private bool _objectBoundSmartTagAutoShow = true; + private bool _enableComponentCache = false; + private bool _enableInSituEditing = true; + /// - /// Public GridSize property. + /// Public GridSize property. /// [SRCategory(nameof(SR.DesignerOptions_LayoutSettings))] [SRDisplayName(nameof(SR.DesignerOptions_GridSizeDisplayName))] [SRDescription(nameof(SR.DesignerOptions_GridSizeDesc))] public virtual Size GridSize { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _gridSize; + set + { + //do some validation checking here + if (value.Width < MinGridSize) value.Width = MinGridSize; + if (value.Height < MinGridSize) value.Height = MinGridSize; + if (value.Width > MaxGridSize) value.Width = MaxGridSize; + if (value.Height > MaxGridSize) value.Height = MaxGridSize; + _gridSize = value; + } } /// - /// Public ShowGrid property. + /// Public ShowGrid property. /// [SRCategory(nameof(SR.DesignerOptions_LayoutSettings))] [SRDisplayName(nameof(SR.DesignerOptions_ShowGridDisplayName))] [SRDescription(nameof(SR.DesignerOptions_ShowGridDesc))] public virtual bool ShowGrid { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _showGrid; + set => _showGrid = value; } /// - /// Public SnapToGrid property. + /// Public SnapToGrid property. /// [SRCategory(nameof(SR.DesignerOptions_LayoutSettings))] [SRDisplayName(nameof(SR.DesignerOptions_SnapToGridDisplayName))] [SRDescription(nameof(SR.DesignerOptions_SnapToGridDesc))] public virtual bool SnapToGrid { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _snapToGrid; + set => _snapToGrid = value; } /// - /// This property enables or disables snaplines in the designer. + /// This property enables or disables snaplines in the designer. /// [SRCategory(nameof(SR.DesignerOptions_LayoutSettings))] [SRDescription(nameof(SR.DesignerOptions_UseSnapLines))] public virtual bool UseSnapLines { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _useSnapLines; + set => _useSnapLines = value; } /// - /// This property enables or disables smart tags in the designer. + /// This property enables or disables smart tags in the designer. /// [SRCategory(nameof(SR.DesignerOptions_LayoutSettings))] [SRDescription(nameof(SR.DesignerOptions_UseSmartTags))] public virtual bool UseSmartTags { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _useSmartTags; + set => _useSmartTags = value; } /// - /// This property enables or disables smart tags in the designer. + /// This property enables or disables smart tags in the designer. /// [SRDisplayName(nameof(SR.DesignerOptions_ObjectBoundSmartTagAutoShowDisplayName))] [SRCategory(nameof(SR.DesignerOptions_ObjectBoundSmartTagSettings))] [SRDescription(nameof(SR.DesignerOptions_ObjectBoundSmartTagAutoShow))] public virtual bool ObjectBoundSmartTagAutoShow { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _objectBoundSmartTagAutoShow; + set => _objectBoundSmartTagAutoShow = value; } /// - /// This property enables or disables the component cache + /// This property enables or disables the component cache /// [SRDisplayName(nameof(SR.DesignerOptions_CodeGenDisplay))] [SRCategory(nameof(SR.DesignerOptions_CodeGenSettings))] [SRDescription(nameof(SR.DesignerOptions_OptimizedCodeGen))] public virtual bool UseOptimizedCodeGeneration { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _enableComponentCache; + set => _enableComponentCache = value; } /// - /// This property enables or disables the InSitu Editing for ToolStrips + /// This property enables or disables the InSitu Editing for ToolStrips /// [SRDisplayName(nameof(SR.DesignerOptions_EnableInSituEditingDisplay))] [SRCategory(nameof(SR.DesignerOptions_EnableInSituEditingCat))] @@ -105,8 +125,8 @@ public virtual bool UseOptimizedCodeGeneration [Browsable(false)] public virtual bool EnableInSituEditing { - get => throw new NotImplementedException(SR.NotImplementedByDesign); - set => throw new NotImplementedException(SR.NotImplementedByDesign); + get => _enableInSituEditing; + set => _enableInSituEditing = value; } } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerUtils.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerUtils.cs new file mode 100644 index 00000000000..f294e624639 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/DesignerUtils.cs @@ -0,0 +1,996 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.ComponentModel.Design.Serialization; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Windows.Forms.Design.Behavior; + +namespace System.Windows.Forms.Design +{ + /// + /// Contains designer utilities. + /// + internal static class DesignerUtils + { + private static Size s_minDragSize = Size.Empty; + + // brush used to draw a 'hover' state over a designer action glyph + private static SolidBrush s_hoverBrush = new SolidBrush(Color.FromArgb(50, SystemColors.Highlight)); + + // brush used to draw the resizeable selection borders around controls/components + private static HatchBrush s_selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.ControlDarkDark, Color.Transparent); + + //Pens and Brushes used via GDI to render our grabhandles + private static IntPtr s_grabHandleFillBrushPrimary = SafeNativeMethods.CreateSolidBrush(ColorTranslator.ToWin32(SystemColors.Window)); + private static IntPtr s_grabHandleFillBrush = SafeNativeMethods.CreateSolidBrush(ColorTranslator.ToWin32(SystemColors.ControlText)); + private static IntPtr s_grabHandlePenPrimary = SafeNativeMethods.CreatePen(NativeMethods.PS_SOLID, 1, ColorTranslator.ToWin32(SystemColors.ControlText)); + private static IntPtr s_grabHandlePen = SafeNativeMethods.CreatePen(NativeMethods.PS_SOLID, 1, ColorTranslator.ToWin32(SystemColors.Window)); + + // The box-like image used as the user is dragging comps from the toolbox + private static Bitmap s_boxImage = null; + public static int BOXIMAGESIZE = 16; + + // selection border size + public static int SELECTIONBORDERSIZE = 1; + + // Although the selection border is only 1, we actually want a 3 pixel hittestarea + public static int SELECTIONBORDERHITAREA = 3; + + // We want to make sure that the 1 pixel selectionborder is centered on the handles. + // The fact that the border is actually 3 pixels wide works like magic. + // If you draw a picture, then you will see why. + + // grabhandle size (diameter) + public static int HANDLESIZE = 7; + // how much should the grabhandle overlap the control + public static int HANDLEOVERLAP = 2; + // we want the selection border to be centered on a grabhandle, so how much do + // we need to offset the border from the control to make that happen + public static int SELECTIONBORDEROFFSET = ((HANDLESIZE - SELECTIONBORDERSIZE) / 2) - HANDLEOVERLAP; + + // no-resize handle size (diameter) + public static int NORESIZEHANDLESIZE = 5; + // we want the selection border to be centered on a grabhandle, so how much do + // we need to offset the border from the control to make that happen + public static int NORESIZEBORDEROFFSET = ((NORESIZEHANDLESIZE - SELECTIONBORDERSIZE) / 2); + + + // lock handle height + public static int LOCKHANDLEHEIGHT = 9; + // total lock handle width + public static int LOCKHANDLEWIDTH = 7; + // how much should the lockhandle overlap the control + public static int LOCKHANDLEOVERLAP = 2; + // we want the selection border to be centered on the no-resize handle, so calculate how many pixels we need + // to offset the selection border from the control -- since the handle is not square, we need one in each direction + public static int LOCKEDSELECTIONBORDEROFFSET_Y = ((LOCKHANDLEHEIGHT - SELECTIONBORDERSIZE) / 2) - LOCKHANDLEOVERLAP; + public static int LOCKEDSELECTIONBORDEROFFSET_X = ((LOCKHANDLEWIDTH - SELECTIONBORDERSIZE) / 2) - LOCKHANDLEOVERLAP; + + // upper rectangle size (diameter) + public static int LOCKHANDLESIZE_UPPER = 5; + // lower rectangle size + public static int LOCKHANDLEHEIGHT_LOWER = 6; + public static int LOCKHANDLEWIDTH_LOWER = 7; + + // Offset used when drawing the upper rect of a lock handle + public static int LOCKHANDLEUPPER_OFFSET = (LOCKHANDLEWIDTH_LOWER - LOCKHANDLESIZE_UPPER) / 2; + + // Offset used when drawing the lower rect of a lock handle + public static int LOCKHANDLELOWER_OFFSET = (LOCKHANDLEHEIGHT - LOCKHANDLEHEIGHT_LOWER); + + public static int CONTAINERGRABHANDLESIZE = 15; + + // delay for showing snaplines on keyboard movements + public static int SNAPELINEDELAY = 1000; + + // min new row/col style size for the table layout panel + public static int MINIMUMSTYLESIZE = 20; + public static int MINIMUMSTYLEPERCENT = 50; + + // min width/height used to create bitmap to paint control into. + public static int MINCONTROLBITMAPSIZE = 1; + + // min size for row/col style during a resize drag operation + public static int MINUMUMSTYLESIZEDRAG = 8; + + // min # of rows/cols for the tablelayoutpanel when it is newly created + public static int DEFAULTROWCOUNT = 2; + public static int DEFAULTCOLUMNCOUNT = 2; + + // size of the col/row grab handle glyphs for teh table layout panel + public static int RESIZEGLYPHSIZE = 4; + + // default value for Form padding if it has not been set in the designer (usability study request) + public static int DEFAULTFORMPADDING = 9; + + // use these value to signify ANY of the right, top, left, center, or bottom alignments with the ContentAlignment enum. + public static readonly ContentAlignment anyTopAlignment = ContentAlignment.TopLeft | ContentAlignment.TopCenter | ContentAlignment.TopRight; + public static readonly ContentAlignment anyMiddleAlignment = ContentAlignment.MiddleLeft | ContentAlignment.MiddleCenter | ContentAlignment.MiddleRight; + + /// + /// Scale all hardcoded sizes if needed + /// + static DesignerUtils() + { + if (!DpiHelper.IsScalingRequired) + { + return; + } + + BOXIMAGESIZE = DpiHelper.LogicalToDeviceUnitsX(BOXIMAGESIZE); + SELECTIONBORDERSIZE = DpiHelper.LogicalToDeviceUnitsX(SELECTIONBORDERSIZE); + SELECTIONBORDERHITAREA = DpiHelper.LogicalToDeviceUnitsX(SELECTIONBORDERHITAREA); + HANDLESIZE = DpiHelper.LogicalToDeviceUnitsX(HANDLESIZE); + HANDLEOVERLAP = DpiHelper.LogicalToDeviceUnitsX(HANDLEOVERLAP); + NORESIZEHANDLESIZE = DpiHelper.LogicalToDeviceUnitsX(NORESIZEHANDLESIZE); + LOCKHANDLEHEIGHT = DpiHelper.LogicalToDeviceUnitsY(LOCKHANDLEHEIGHT); + LOCKHANDLEWIDTH = DpiHelper.LogicalToDeviceUnitsX(LOCKHANDLEWIDTH); + LOCKHANDLEOVERLAP = DpiHelper.LogicalToDeviceUnitsX(LOCKHANDLEOVERLAP); + LOCKHANDLESIZE_UPPER = DpiHelper.LogicalToDeviceUnitsX(LOCKHANDLESIZE_UPPER); + LOCKHANDLEHEIGHT_LOWER = DpiHelper.LogicalToDeviceUnitsY(LOCKHANDLEHEIGHT_LOWER); + LOCKHANDLEWIDTH_LOWER = DpiHelper.LogicalToDeviceUnitsX(LOCKHANDLEWIDTH_LOWER); + CONTAINERGRABHANDLESIZE = DpiHelper.LogicalToDeviceUnitsX(CONTAINERGRABHANDLESIZE); + RESIZEGLYPHSIZE = DpiHelper.LogicalToDeviceUnitsX(RESIZEGLYPHSIZE); + + SELECTIONBORDEROFFSET = ((HANDLESIZE - SELECTIONBORDERSIZE) / 2) - HANDLEOVERLAP; + NORESIZEBORDEROFFSET = ((NORESIZEHANDLESIZE - SELECTIONBORDERSIZE) / 2); + LOCKEDSELECTIONBORDEROFFSET_Y = ((LOCKHANDLEHEIGHT - SELECTIONBORDERSIZE) / 2) - LOCKHANDLEOVERLAP; + LOCKEDSELECTIONBORDEROFFSET_X = ((LOCKHANDLEWIDTH - SELECTIONBORDERSIZE) / 2) - LOCKHANDLEOVERLAP; + LOCKHANDLEUPPER_OFFSET = (LOCKHANDLEWIDTH_LOWER - LOCKHANDLESIZE_UPPER) / 2; + LOCKHANDLELOWER_OFFSET = (LOCKHANDLEHEIGHT - LOCKHANDLEHEIGHT_LOWER); + } + + /// + /// Used when the user clicks and drags a toolbox item onto the documentdesigner - this is the small box that is painted beneath the mouse pointer. + /// + public static Image BoxImage + { + get + { + if (s_boxImage == null) + { + s_boxImage = new Bitmap(BOXIMAGESIZE, BOXIMAGESIZE, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + using (Graphics g = Graphics.FromImage(s_boxImage)) + { + g.FillRectangle(new SolidBrush(SystemColors.InactiveBorder), 0, 0, BOXIMAGESIZE, BOXIMAGESIZE); + g.DrawRectangle(new Pen(SystemColors.ControlDarkDark), 0, 0, BOXIMAGESIZE - 1, BOXIMAGESIZE - 1); + } + } + return s_boxImage; + } + } + + /// + /// Used by Designer action glyphs to render a 'mouse hover' state. + /// + public static Brush HoverBrush + { + get + { + return s_hoverBrush; + } + } + + /// + /// Demand created size used to determine how far the user needs to drag the mouse before a drag operation starts. + /// + public static Size MinDragSize + { + get + { + if (s_minDragSize == Size.Empty) + { + Size minDrag = SystemInformation.DragSize; + Size minDblClick = SystemInformation.DoubleClickSize; + s_minDragSize.Width = Math.Max(minDrag.Width, minDblClick.Width); + s_minDragSize.Height = Math.Max(minDrag.Height, minDblClick.Height); + } + return s_minDragSize; + } + } + + public static Point LastCursorPoint + { + get + { + int lastXY = SafeNativeMethods.GetMessagePos(); + return new Point(NativeMethods.Util.SignedLOWORD(lastXY), NativeMethods.Util.SignedHIWORD(lastXY)); + } + } + + // Recreate the brushes - behaviorservice calls this when the user preferences changes + public static void SyncBrushes() + { + s_hoverBrush.Dispose(); + s_hoverBrush = new SolidBrush(Color.FromArgb(50, SystemColors.Highlight)); + + s_selectionBorderBrush.Dispose(); + s_selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.ControlDarkDark, Color.Transparent); + + SafeNativeMethods.DeleteObject(new HandleRef(null, s_grabHandleFillBrushPrimary)); + s_grabHandleFillBrushPrimary = SafeNativeMethods.CreateSolidBrush(ColorTranslator.ToWin32(SystemColors.Window)); + + SafeNativeMethods.DeleteObject(new HandleRef(null, s_grabHandleFillBrush)); + s_grabHandleFillBrush = SafeNativeMethods.CreateSolidBrush(ColorTranslator.ToWin32(SystemColors.ControlText)); + + SafeNativeMethods.DeleteObject(new HandleRef(null, s_grabHandlePenPrimary)); + s_grabHandlePenPrimary = SafeNativeMethods.CreatePen(NativeMethods.PS_SOLID, 1, ColorTranslator.ToWin32(SystemColors.ControlText)); + + SafeNativeMethods.DeleteObject(new HandleRef(null, s_grabHandlePen)); + s_grabHandlePen = SafeNativeMethods.CreatePen(NativeMethods.PS_SOLID, 1, ColorTranslator.ToWin32(SystemColors.Window)); + } + + /// + /// Draws a ControlDarkDark border around the given image. + /// + private static void DrawDragBorder(Graphics g, Size imageSize, int borderSize, Color backColor) + { + + Pen pen = SystemPens.ControlDarkDark; + if (backColor != Color.Empty && backColor.GetBrightness() < .5) + { + pen = SystemPens.ControlLight; + } + + // draw a border w/o the corners connecting + g.DrawLine(pen, 1, 0, imageSize.Width - 2, 0); + g.DrawLine(pen, 1, imageSize.Height - 1, imageSize.Width - 2, imageSize.Height - 1); + g.DrawLine(pen, 0, 1, 0, imageSize.Height - 2); + g.DrawLine(pen, imageSize.Width - 1, 1, imageSize.Width - 1, imageSize.Height - 2); + + // loop through drawing inner-rects until we get the proper thickness + for (int i = 1; i < borderSize; i++) + { + g.DrawRectangle(pen, i, i, imageSize.Width - (2 + i), imageSize.Height - (2 + i)); + } + } + + /// + /// Used for drawing the borders around controls that are being resized + /// + public static void DrawResizeBorder(Graphics g, Region resizeBorder, Color backColor) + { + Brush brush = SystemBrushes.ControlDarkDark; + if (backColor != Color.Empty && backColor.GetBrightness() < .5) + { + brush = SystemBrushes.ControlLight; + } + + g.FillRegion(brush, resizeBorder); + } + + /// + /// Used for drawing the frame when doing a mouse drag + /// + public static void DrawFrame(Graphics g, Region resizeBorder, FrameStyle style, Color backColor) + { + Brush brush; + Color color = SystemColors.ControlDarkDark; + if (backColor != Color.Empty && backColor.GetBrightness() < .5) + { + color = SystemColors.ControlLight; + } + + switch (style) + { + case FrameStyle.Dashed: + brush = new HatchBrush(HatchStyle.Percent50, color, Color.Transparent); + break; + case FrameStyle.Thick: + default: + brush = new SolidBrush(color); + break; + } + + g.FillRegion(brush, resizeBorder); + brush.Dispose(); + } + + /// + /// Used for drawing the grabhandles around sizeable selected controls and components. + /// + public static void DrawGrabHandle(Graphics graphics, Rectangle bounds, bool isPrimary, Glyph glyph) + { + IntPtr hDC = graphics.GetHdc(); + try + { + //set our pen and brush based on primary selection + IntPtr oldBrush = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, isPrimary ? s_grabHandleFillBrushPrimary : s_grabHandleFillBrush)); + IntPtr oldPen = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, isPrimary ? s_grabHandlePenPrimary : s_grabHandlePen)); + + //draw our rounded rect grabhandle + SafeNativeMethods.RoundRect(new HandleRef(glyph, hDC), bounds.Left, bounds.Top, bounds.Right, bounds.Bottom, 2, 2); + + //restore old pen and brush + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldBrush)); + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldPen)); + } + finally + { + graphics.ReleaseHdcInternal(hDC); + } + } + + /// + /// Used for drawing the no-resize handle for non-resizeable selected controls and components. + /// + public static void DrawNoResizeHandle(Graphics graphics, Rectangle bounds, bool isPrimary, Glyph glyph) + { + IntPtr hDC = graphics.GetHdc(); + try + { + //set our pen and brush based on primary selection + IntPtr oldBrush = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, isPrimary ? s_grabHandleFillBrushPrimary : s_grabHandleFillBrush)); + IntPtr oldPen = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, s_grabHandlePenPrimary)); + + //draw our rect no-resize handle + SafeNativeMethods.Rectangle(new HandleRef(glyph, hDC), bounds.Left, bounds.Top, bounds.Right, bounds.Bottom); + + //restore old pen and brush + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldBrush)); + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldPen)); + } + finally + { + graphics.ReleaseHdcInternal(hDC); + } + } + + /// + /// Used for drawing the lock handle for locked selected controls and components. + /// + public static void DrawLockedHandle(Graphics graphics, Rectangle bounds, bool isPrimary, Glyph glyph) + { + IntPtr hDC = graphics.GetHdc(); + try + { + + IntPtr oldPen = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, s_grabHandlePenPrimary)); + + // Upper rect - upper rect is always filled with the primary brush + IntPtr oldBrush = SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, s_grabHandleFillBrushPrimary)); + SafeNativeMethods.RoundRect(new HandleRef(glyph, hDC), bounds.Left + LOCKHANDLEUPPER_OFFSET, bounds.Top, + bounds.Left + LOCKHANDLEUPPER_OFFSET + LOCKHANDLESIZE_UPPER, bounds.Top + LOCKHANDLESIZE_UPPER, 2, 2); + + // Lower rect - its fillbrush depends on the primary selection + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, isPrimary ? s_grabHandleFillBrushPrimary : s_grabHandleFillBrush)); + SafeNativeMethods.Rectangle(new HandleRef(glyph, hDC), bounds.Left, bounds.Top + LOCKHANDLELOWER_OFFSET, bounds.Right, bounds.Bottom); + + //restore old pen and brush + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldBrush)); + SafeNativeMethods.SelectObject(new HandleRef(glyph, hDC), new HandleRef(glyph, oldPen)); + } + finally + { + graphics.ReleaseHdcInternal(hDC); + } + } + + + /// + /// Uses the lockedBorderBrush to draw a 'locked' border on the given Graphics at the specified bounds. + /// + public static void DrawSelectionBorder(Graphics graphics, Rectangle bounds) + { + graphics.FillRectangle(s_selectionBorderBrush, bounds); + } + + /// + /// Used to generate an image that represents the given control. + /// First, this method will call the 'GenerateSnapShotWithWM_PRINT' method on the control. + /// If we believe that this method did not return us a valid image (caused by some comctl/ax controls not properly responding to a wm_print) then we will attempt to do a bitblt of the control instead. + /// + public static void GenerateSnapShot(Control control, ref Image image, int borderSize, double opacity, Color backColor) + { + + // GenerateSnapShot will return a boolean value indicating if the control returned an image or not... + if (!GenerateSnapShotWithWM_PRINT(control, ref image)) + { + // here, we failed to get the image on wmprint - so try bitblt + GenerateSnapShotWithBitBlt(control, ref image); + // if we still failed - we'll just fall though, put up a border around an empty area and call it good enough + } + + // set the opacity + if (opacity < 1.0 && opacity > 0.0) + { + // make this semi-transparent + SetImageAlpha((Bitmap)image, opacity); + } + + // draw a drag border around this thing + if (borderSize > 0) + { + using (Graphics g = Graphics.FromImage(image)) + { + DrawDragBorder(g, image.Size, borderSize, backColor); + } + } + + } + + /// + /// Retrieves the width and height of a selection border grab handle. + /// Designers may need this to properly position their user interfaces. + /// + public static Size GetAdornmentDimensions(AdornmentType adornmentType) + { + switch (adornmentType) + { + case AdornmentType.GrabHandle: + return new Size(HANDLESIZE, HANDLESIZE); + case AdornmentType.ContainerSelector: + case AdornmentType.Maximum: + return new Size(CONTAINERGRABHANDLESIZE, CONTAINERGRABHANDLESIZE); + } + return new Size(0, 0); + } + + public static bool UseSnapLines(IServiceProvider provider) + { + bool useSnapLines = true; + object optionValue = null; + if (provider.GetService(typeof(DesignerOptionService)) is DesignerOptionService options) + { + PropertyDescriptor snaplinesProp = options.Options.Properties["UseSnapLines"]; + if (snaplinesProp != null) + { + optionValue = snaplinesProp.GetValue(null); + } + } + + if (optionValue != null && optionValue is bool) + { + useSnapLines = (bool)optionValue; + } + return useSnapLines; + } + + public static object GetOptionValue(IServiceProvider provider, string name) + { + object optionValue = null; + if (provider != null) + { + if (provider.GetService(typeof(DesignerOptionService)) is DesignerOptionService desOpts) + { + PropertyDescriptor prop = desOpts.Options.Properties[name]; + if (prop != null) + { + optionValue = prop.GetValue(null); + } + } + else + { + if (provider.GetService(typeof(IDesignerOptionService)) is IDesignerOptionService optSvc) + { + optionValue = optSvc.GetOptionValue("WindowsFormsDesigner\\General", name); + } + } + } + return optionValue; + } + + /// + /// Uses BitBlt to geta snapshot of the control + /// + public static void GenerateSnapShotWithBitBlt(Control control, ref Image image) + { + // get the DC's and create our image + HandleRef hWnd = new HandleRef(control, control.Handle); + IntPtr controlDC = UnsafeNativeMethods.GetDC(hWnd); + image = new Bitmap(Math.Max(control.Width, MINCONTROLBITMAPSIZE), Math.Max(control.Height, MINCONTROLBITMAPSIZE), System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + + using (Graphics gDest = Graphics.FromImage(image)) + { + if (control.BackColor == Color.Transparent) + { + gDest.Clear(SystemColors.Control); + } + + IntPtr destDC = gDest.GetHdc(); + + // perform our bitblit operation to push the image into the dest bitmap + SafeNativeMethods.BitBlt(destDC, 0, 0, image.Width, image.Height, controlDC, 0, 0, 0xcc0020/*RasterOp.SOURCE*/); + //clean up all our handles and what not + gDest.ReleaseHdc(destDC); + } + } + + /// + /// Uses WM_PRINT to get a snapshot of the control. This method will return true if the control properly responded to the wm_print message. + /// + public static bool GenerateSnapShotWithWM_PRINT(Control control, ref Image image) + { + IntPtr hWnd = control.Handle; + image = new Bitmap(Math.Max(control.Width, MINCONTROLBITMAPSIZE), Math.Max(control.Height, MINCONTROLBITMAPSIZE), System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + // Have to do this BEFORE we set the testcolor. + if (control.BackColor == Color.Transparent) + { + using (Graphics g = Graphics.FromImage(image)) + { + g.Clear(SystemColors.Control); + } + } + + // To validate that the control responded to the wm_print message, we pre-populate the bitmap with a colored center pixel. We assume that the control _did not_ respond to wm_print if these center pixel is still this value + Color testColor = Color.FromArgb(255, 252, 186, 238); + ((Bitmap)image).SetPixel(image.Width / 2, image.Height / 2, testColor); + + using (Graphics g = Graphics.FromImage(image)) + { + IntPtr hDc = g.GetHdc(); + //send the actual wm_print message + NativeMethods.SendMessage(hWnd, NativeMethods.WM_PRINT, hDc, (IntPtr)(NativeMethods.PRF_CHILDREN | NativeMethods.PRF_CLIENT | NativeMethods.PRF_ERASEBKGND | NativeMethods.PRF_NONCLIENT)); + g.ReleaseHdc(hDc); + } + + //now check to see if our center pixel was cleared, if not then our wm_print failed + if (((Bitmap)image).GetPixel(image.Width / 2, image.Height / 2).Equals(testColor)) + { + //wm_print failed + return false; + } + + return true; + } + + /// + /// Used by the Glyphs and ComponentTray to determine the Top, Left, Right, Bottom and Body bound rects related to their original bounds and bordersize. + /// + public static Rectangle GetBoundsForSelectionType(Rectangle originalBounds, SelectionBorderGlyphType type, int borderSize) + { + Rectangle bounds = Rectangle.Empty; + switch (type) + { + case SelectionBorderGlyphType.Top: + bounds = new Rectangle(originalBounds.Left - borderSize, originalBounds.Top - borderSize, originalBounds.Width + 2 * borderSize, borderSize); + break; + case SelectionBorderGlyphType.Bottom: + bounds = new Rectangle(originalBounds.Left - borderSize, originalBounds.Bottom, originalBounds.Width + 2 * borderSize, borderSize); + break; + case SelectionBorderGlyphType.Left: + bounds = new Rectangle(originalBounds.Left - borderSize, originalBounds.Top - borderSize, borderSize, originalBounds.Height + 2 * borderSize); + break; + case SelectionBorderGlyphType.Right: + bounds = new Rectangle(originalBounds.Right, originalBounds.Top - borderSize, borderSize, originalBounds.Height + 2 * borderSize); + break; + case SelectionBorderGlyphType.Body: + bounds = originalBounds; + break; + } + return bounds; + } + + /// + /// Used by the Glyphs and ComponentTray to determine the Top, Left, Right, Bottom and Body bound rects related to their original bounds and bordersize. + /// Offset - how many pixels between the border glyph and the control + /// + private static Rectangle GetBoundsForSelectionType(Rectangle originalBounds, SelectionBorderGlyphType type, int bordersize, int offset) + { + Rectangle bounds = GetBoundsForSelectionType(originalBounds, type, bordersize); + if (offset != 0) + { + switch (type) + { + case SelectionBorderGlyphType.Top: + bounds.Offset(-offset, -offset); + bounds.Width += 2 * offset; + break; + case SelectionBorderGlyphType.Bottom: + bounds.Offset(-offset, offset); + bounds.Width += 2 * offset; + break; + case SelectionBorderGlyphType.Left: + bounds.Offset(-offset, -offset); + bounds.Height += 2 * offset; + break; + case SelectionBorderGlyphType.Right: + bounds.Offset(offset, -offset); + bounds.Height += 2 * offset; + break; + case SelectionBorderGlyphType.Body: + bounds = originalBounds; + break; + } + } + return bounds; + } + + /// + /// Used by the Glyphs and ComponentTray to determine the Top, Left, Right, Bottom and Body bound rects related to their original bounds and bordersize. + /// + public static Rectangle GetBoundsForSelectionType(Rectangle originalBounds, SelectionBorderGlyphType type) + { + return GetBoundsForSelectionType(originalBounds, type, DesignerUtils.SELECTIONBORDERSIZE, SELECTIONBORDEROFFSET); + } + + public static Rectangle GetBoundsForNoResizeSelectionType(Rectangle originalBounds, SelectionBorderGlyphType type) + { + return GetBoundsForSelectionType(originalBounds, type, DesignerUtils.SELECTIONBORDERSIZE, NORESIZEBORDEROFFSET); + } + + /// + /// Identifes where the text baseline for our control which should be based on bounds, padding, font, and textalignment. + /// + public static int GetTextBaseline(Control ctrl, ContentAlignment alignment) + { + //determine the actual client area we are working in (w/padding) + Rectangle face = ctrl.ClientRectangle; + + //get the font metrics via gdi + int fontAscent = 0; + int fontHeight = 0; + using (Graphics g = ctrl.CreateGraphics()) + { + IntPtr dc = g.GetHdc(); + IntPtr hFont = ctrl.Font.ToHfont(); + IntPtr hFontOld; + try + { + hFontOld = SafeNativeMethods.SelectObject(new HandleRef(ctrl, dc), new HandleRef(ctrl, hFont)); + NativeMethods.TEXTMETRIC metrics = new NativeMethods.TEXTMETRIC(); + SafeNativeMethods.GetTextMetrics(new HandleRef(ctrl, dc), metrics); + //add the font ascent to the baseline + fontAscent = metrics.tmAscent + 1; + fontHeight = metrics.tmHeight; + SafeNativeMethods.SelectObject(new HandleRef(ctrl, dc), new HandleRef(ctrl, hFontOld)); + } + finally + { + SafeNativeMethods.DeleteObject(new HandleRef(ctrl.Font, hFont)); + g.ReleaseHdc(dc); + } + } + + //now add it all up + if ((alignment & anyTopAlignment) != 0) + { + return face.Top + fontAscent; + } + else if ((alignment & anyMiddleAlignment) != 0) + { + return face.Top + (face.Height / 2) - (fontHeight / 2) + fontAscent; + } + else + { + return face.Bottom - fontHeight + fontAscent; + } + } + + /// + /// Called by the ParentControlDesigner when creating a new control - this will update the new control's bounds with the proper toolbox/snapline information that has been stored off + /// isMirrored - Is the ParentControlDesigner mirrored? If so, we need to offset for that. This is because all snapline stuff is done using a LTR coordinate system + /// + public static Rectangle GetBoundsFromToolboxSnapDragDropInfo(ToolboxSnapDragDropEventArgs e, Rectangle originalBounds, bool isMirrored) + { + Rectangle newBounds = originalBounds; + // this should always be the case 'cause we don't create 'e' unless we have an offset + if (e.Offset != Point.Empty) + { + //snap either up or down depending on offset + if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Top) != 0) + { + newBounds.Y += e.Offset.Y;//snap to top - so move up our bounds + } + else if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Bottom) != 0) + { + newBounds.Y = originalBounds.Y - originalBounds.Height + e.Offset.Y; + } + + //snap either left or right depending on offset + if (!isMirrored) + { + if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Left) != 0) + { + newBounds.X += e.Offset.X;//snap to left- + } + else if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Right) != 0) + { + newBounds.X = originalBounds.X - originalBounds.Width + e.Offset.X; + } + } + else + { + // ParentControlDesigner is RTL, that means that the origin is upper-right, not upper-left + if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Left) != 0) + { + // e.Offset.X is negative when we snap to left + newBounds.X = originalBounds.X - originalBounds.Width - e.Offset.X; + } + else if ((e.SnapDirections & ToolboxSnapDragDropEventArgs.SnapDirection.Right) != 0) + { + // e.Offset.X is positive when we snao to right + newBounds.X -= e.Offset.X; + } + } + } + return newBounds; + } + + /// + /// Determine a unique site name for a component, starting from a base name. Return value should be passed into the Container.Add() method. If null is returned, this just means "let container generate a default name based on component type". + /// + public static string GetUniqueSiteName(IDesignerHost host, string name) + { + // Item has no explicit name, so let host generate a type-based name instead + if (string.IsNullOrEmpty(name)) + { + return null; + } + + // Get the name creation service from the designer host + INameCreationService nameCreationService = (INameCreationService)host.GetService(typeof(INameCreationService)); + if (nameCreationService == null) + { + return null; + } + + // See if desired name is already in use + object existingComponent = host.Container.Components[name]; + + if (existingComponent == null) + { + // Name is not in use - but make sure that it contains valid characters before using it! + return nameCreationService.IsValidName(name) ? name : null; + } + else + { + // Name is in use (and therefore basically valid), so start appending numbers + string nameN = name; + for (int i = 1; !nameCreationService.IsValidName(nameN); ++i) + { + nameN = name + i.ToString(CultureInfo.InvariantCulture); + } + return nameN; + } + } + + /// + /// Applies the given opacity to the image + /// + private static unsafe void SetImageAlpha(Bitmap b, double opacity) + { + if (opacity == 1.0) + { + return; + } + + byte[] alphaValues = new byte[256]; + + // precompute all the possible alpha values into an array so we don't do multiplications in the loop + for (int i = 0; i < alphaValues.Length; i++) + { + alphaValues[i] = (byte)(i * opacity); + } + + // lock the data in ARGB format. + BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + try + { + // compute the number of pixels that we're modifying. + int pixels = data.Height * data.Width; + int* pPixels = (int*)data.Scan0; + + // have the compiler figure out where to stop for us by doing the pointer math + byte* maxAddr = (byte*)(pPixels + pixels); + + // now run through the pixels only modifyng the A byte + for (byte* addr = (byte*)(pPixels) + 3; addr < maxAddr; addr += 4) + { + // the new value is just an index into our precomputed value array from above. + *addr = alphaValues[*addr]; + } + } + finally + { + // now, apply the data back to the bitmap. + b.UnlockBits(data); + } + + } + + /// + /// This method removes types that are generics from the input collection + /// + public static ICollection FilterGenericTypes(ICollection types) + { + if (types == null || types.Count == 0) + return types; + + //now we get each Type and add it to the destination collection if its not a generic + ArrayList final = new ArrayList(types.Count); + foreach (Type t in types) + { + if (!t.ContainsGenericParameters) + { + final.Add(t); + } + } + return final; + } + + /// + /// Checks the given container, substituting any nested container with its owning container. + /// Ensures that a SplitterPanel in a SplitContainer returns the same container as other form components, since SplitContainer sites its two SplitterPanels inside a nested container. + /// + public static IContainer CheckForNestedContainer(IContainer container) + { + if (container is NestedContainer nestedContainer) + { + return nestedContainer.Owner.Site.Container; + } + else + { + return container; + } + } + + /// + /// Used to create copies of the objects that we are dragging in a drag operation + /// + public static ICollection CopyDragObjects(ICollection objects, IServiceProvider svcProvider) + { + if (objects == null || svcProvider == null) + { + Debug.Fail("Invalid parameter passed to DesignerUtils.CopyObjects."); + return null; + } + + Cursor oldCursor = Cursor.Current; + try + { + Cursor.Current = Cursors.WaitCursor; + ComponentSerializationService css = svcProvider.GetService(typeof(ComponentSerializationService)) as ComponentSerializationService; + IDesignerHost host = svcProvider.GetService(typeof(IDesignerHost)) as IDesignerHost; + + Debug.Assert(css != null, "No component serialization service -- we cannot copy the objects"); + Debug.Assert(host != null, "No host -- we cannot copy the objects"); + if (css != null && host != null) + { + + SerializationStore store = null; + + store = css.CreateStore(); + + // Get all the objects, meaning we want the children too + ICollection copyObjects = GetCopySelection(objects, host); + + // The serialization service does not (yet) handle serializing collections + foreach (IComponent comp in copyObjects) + { + css.Serialize(store, comp); + } + store.Close(); + copyObjects = css.Deserialize(store); + + // Now, copyObjects contains a flattened list of all the controls contained in the original drag objects, that's not what we want to return. + // We only want to return the root drag objects, so that the caller gets an identical copy - identical in terms of objects.Count + ArrayList newObjects = new ArrayList(objects.Count); + foreach (IComponent comp in copyObjects) + { + Control c = comp as Control; + if (c != null && c.Parent == null) + { + newObjects.Add(comp); + } + else if (c == null) + { // this happens when we are dragging a toolstripitem + // TODO: Can we remove the ToolStrip specific code? + if (comp is ToolStripItem item && item.GetCurrentParent() == null) + { + newObjects.Add(comp); + } + } + } + Debug.Assert(newObjects.Count == objects.Count, "Why is the count of the copied objects not the same?"); + return newObjects; + } + } + finally + { + Cursor.Current = oldCursor; + } + return null; + } + + private static ICollection GetCopySelection(ICollection objects, IDesignerHost host) + { + if (objects == null || host == null) + { + return null; + } + + ArrayList copySelection = new ArrayList(); + foreach (IComponent comp in objects) + { + copySelection.Add(comp); + GetAssociatedComponents(comp, host, copySelection); + } + return copySelection; + } + + internal static void GetAssociatedComponents(IComponent component, IDesignerHost host, ArrayList list) + { + if (host == null) + { + return; + } + + if (!(host.GetDesigner(component) is ComponentDesigner designer)) + { + return; + } + + foreach (IComponent childComp in designer.AssociatedComponents) + { + if (childComp.Site != null) + { + list.Add(childComp); + GetAssociatedComponents(childComp, host, list); + } + } + } + + private static int TreeView_GetExtendedStyle(IntPtr handle) + { + IntPtr ptr = NativeMethods.SendMessage(handle, NativeMethods.TVM_GETEXTENDEDSTYLE, IntPtr.Zero, IntPtr.Zero); + return ptr.ToInt32(); + } + + private static void TreeView_SetExtendedStyle(IntPtr handle, int extendedStyle, int mask) + { + NativeMethods.SendMessage(handle, NativeMethods.TVM_SETEXTENDEDSTYLE, new IntPtr(mask), new IntPtr(extendedStyle)); + } + + /// + /// Modify a WinForms TreeView control to use the new Explorer style theme + /// + /// The tree view control to modify + public static void ApplyTreeViewThemeStyles(TreeView treeView) + { + if (treeView == null) + { + throw new ArgumentNullException(nameof(treeView)); + } + + treeView.HotTracking = true; + treeView.ShowLines = false; + + IntPtr hwnd = treeView.Handle; + SafeNativeMethods.SetWindowTheme(hwnd, "Explorer", null); + int exstyle = TreeView_GetExtendedStyle(hwnd); + exstyle |= NativeMethods.TVS_EX_DOUBLEBUFFER | NativeMethods.TVS_EX_FADEINOUTEXPANDOS; + TreeView_SetExtendedStyle(hwnd, exstyle, 0); + } + + private static void ListView_SetExtendedListViewStyleEx(IntPtr handle, int mask, int extendedStyle) + { + NativeMethods.SendMessage(handle, NativeMethods.LVM_SETEXTENDEDLISTVIEWSTYLE, new IntPtr(mask), new IntPtr(extendedStyle)); + } + + /// + /// Modify a WinForms ListView control to use the new Explorer style theme + /// + /// The list view control to modify + public static void ApplyListViewThemeStyles(ListView listView) + { + if (listView == null) + { + throw new ArgumentNullException(nameof(listView)); + } + + IntPtr hwnd = listView.Handle; + SafeNativeMethods.SetWindowTheme(hwnd, "Explorer", null); + ListView_SetExtendedListViewStyleEx(hwnd, NativeMethods.LVS_EX_DOUBLEBUFFER, NativeMethods.LVS_EX_DOUBLEBUFFER); + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IMenuStatusHandler.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IMenuStatusHandler.cs new file mode 100644 index 00000000000..f02f255f501 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IMenuStatusHandler.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel.Design; + +namespace System.Windows.Forms.Design +{ + /// + /// We plug this interface into the designer event service for overriding menu commands. + /// + internal interface IMenuStatusHandler + { + /// + /// CommandSet will check with this handler on each status update to see if the handler wants to override the availability of this command. + /// + bool OverrideInvoke(MenuCommand cmd); + + /// + /// CommandSet will check with this handler on each status update to see if the handler wants to override the availability of this command. + /// + bool OverrideStatus(MenuCommand cmd); + } + +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IOverlayService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IOverlayService.cs new file mode 100644 index 00000000000..a36e6377c24 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/IOverlayService.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Drawing; + +namespace System.Windows.Forms.Design +{ + /// + /// IOverlayService is a service that supports adding simple overlay windows to a design surface. Overlay windows can be used to paint extra glyphs on top of existing controls. + /// Once an overlay is added, it will be forced on top of the Z-order for the other controls and overlays. + /// If you want the overlay to be transparent, then you must do this work yourself. + /// A typical way to make an overlay control transparent is to use the method setRegion on the control class to define the non-transparent portion of the contro. + /// + internal interface IOverlayService + { + /// + /// Pushes the given control on top of the overlay list. This is a "push" operation, meaning that it forces this control to the top of the existing overlay list. + /// + int PushOverlay(Control control); + + /// + /// Removes the given control from the overlay list. Unlike pushOverlay, this can remove a control from the middle of the overlay list. + /// + void RemoveOverlay(Control control); + + /// + /// Inserts the given control from the overlay list. You need to pass the index of the overlay. + /// + void InsertOverlay(Control control, int index); + + /// + /// Invalidates the overlays + /// + void InvalidateOverlays(Rectangle screenRectangle); + + /// + /// Invalidates the overlays + /// + void InvalidateOverlays(Region screenRegion); + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISelectionUIService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISelectionUIService.cs new file mode 100644 index 00000000000..97d946759bb --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISelectionUIService.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Drawing; + +namespace System.Windows.Forms.Design +{ + /// + /// The selection UI service is used to provide a standard user interface for selection across designers. + /// Using this service is optional, but is recommended to provide a standard UI component selection. + /// + internal interface ISelectionUIService + { + /// + /// Determines if the selection UI is shown or not. + /// + bool Visible { get; set; } + + /// + /// Adds an event handler to the ContainerSelectorActive event. + /// This event is fired whenever the user interacts with the container selector in a manor that would indicate that the selector should continued to be displayed. + /// Since the container selector normally will vanish after a timeout, designers should listen to this event and reset the timeout when this event occurs. + /// + event ContainerSelectorActiveEventHandler ContainerSelectorActive; + + /// + /// Assigns a selection UI handler to a given component. + /// The handler will be called when the UI service needs information about the component. + /// A single selection UI handler can be assigned to multiple components. + /// When multiple components are dragged, only a single handler may control the drag. + /// Because of this, only components that are assigned the same handler as the primary selection are included in drag operations. + /// A selection UI handler is automatically unassigned when the component is removed from the container or disposed. + /// + void AssignSelectionUIHandler(object component, ISelectionUIHandler handler); + + void ClearSelectionUIHandler(object component, ISelectionUIHandler handler); + + /// + /// This can be called by an outside party to begin a drag of the currently selected set of components. + /// At least one designer must have added a UI handler or else this method will always return false. + /// + bool BeginDrag(SelectionRules rules, int initialX, int initialY); + + /// + /// This can be used to determine if the user is in the middle of a drag operation. + /// + bool Dragging { get; } + + /// + /// Called by an outside party to update drag information. This can only be called after a successful call to beginDrag. + /// + void DragMoved(Rectangle offset); + + /// + /// Called by an outside party to finish a drag operation. This can only be called after a successful call to beginDrag. + /// + void EndDrag(bool cancel); + + /// + /// Filters the set of selected components. + /// The selection service will retrieve all components that are currently selected. + /// This method allows you to filter this set down to components that match your criteria. + /// The selectionRules parameter must contain one or more flags from the SelectionRules class. + /// These flags allow you to constrain the set of selected objects to visible, movable, sizeable or all objects. + /// + object[] FilterSelection(object[] components, SelectionRules selectionRules); + + /// + /// Retrieves the width and height of a selection border grab handle. + /// Designers may need this to properly position their user interfaces. + /// + Size GetAdornmentDimensions(AdornmentType adornmentType); + + /// + /// Tests to determine if the given screen coordinate is over an adornment for the specified component. This will only return true if the adornment, and selection UI, is visible. + /// + bool GetAdornmentHitTest(object component, Point pt); + + /// + /// Gets a value indicating whether the specified component is the currently selected container. + /// + bool GetContainerSelected(object component); + + /// + /// Retrieves a set of flags that define rules for the selection. Selection rules indicate if the given component can be moved or sized, for example. + /// + SelectionRules GetSelectionRules(object component); + + /// + /// Allows you to configure the style of the selection frame that a component uses. + /// This is useful if your component supports different modes of operation (such as an in-place editing mode and a static design mode). + /// Where possible, you should leave the selection style as is and use the design-time hit testing feature of the IDesigner interface to provide features at design time. + /// The value of style must be one of the SelectionStyle enum values. + /// The selection style is only valid for the duration that the component is selected. + /// + SelectionStyles GetSelectionStyle(object component); + + /// + /// Changes the container selection status of the specified component. + /// + void SetContainerSelected(object component, bool selected); + + /// + /// Allows you to configure the style of the selection frame that a component uses. + /// This is useful if your component supports different modes of operation (such as an in-place editing mode and a static design mode). + /// Where possible, you should leave the selection style as is and use the design-time hit testing feature of the IDesigner interface to provide features at design time. + /// The value of style must be one of the SelectionStyle enum values. + /// The selection style is only valid for the duration that the component is selected. + /// + void SetSelectionStyle(object component, SelectionStyles style); + + /// + /// This should be called when a component has been moved, sized or re-parented, but the change was not the result of a property change. + /// All property changes are monitored by the selection UI service, so this is automatic most of the time. + /// There are times, however, when a component may be moved without a property change notification occurring. + /// Scrolling an auto scroll Win32 form is an example of this. + /// This method simply re-queries all currently selected components for their bounds and udpates the selection handles for any that have changed. + /// + void SyncSelection(); + + /// + /// This should be called when a component's property changed, that the designer thinks should result in a selection UI change. + /// This method simply re-queries all currently selected components for their bounds and udpates the selection handles for any that have changed. + /// + void SyncComponent(object component); + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISplitWindowService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISplitWindowService.cs new file mode 100644 index 00000000000..e9f915ee213 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ISplitWindowService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + /// + /// Supports the hosting of several 'pane' windows + /// separated by splitter bars. + /// + internal interface ISplitWindowService + { + /// + /// Requests the service to add a window 'pane'. + /// + void AddSplitWindow(Control window); + + /// + /// Requests the service to remove a window 'pane'. + /// + void RemoveSplitWindow(Control window); + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InheritanceUI.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InheritanceUI.cs new file mode 100644 index 00000000000..b0db168b5ae --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InheritanceUI.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; + +namespace System.Windows.Forms.Design +{ + /// + /// This class handles the user interface for inherited components. + /// + internal class InheritanceUI + { + private static Bitmap inheritanceGlyph; + private static Rectangle inheritanceGlyphRect; + private ToolTip tooltip; + + /// + /// The bitmap we use to show inheritance. + /// + public Bitmap InheritanceGlyph + { + get + { + if (inheritanceGlyph == null) + { + inheritanceGlyph = new Bitmap(typeof(InheritanceUI), "InheritedGlyph.bmp"); + inheritanceGlyph.MakeTransparent(); + if (DpiHelper.IsScalingRequired) + { + DpiHelper.ScaleBitmapLogicalToDevice(ref inheritanceGlyph); + } + } + return inheritanceGlyph; + } + } + + /// + /// The rectangle surrounding the glyph. + /// + public Rectangle InheritanceGlyphRectangle + { + get + { + if (inheritanceGlyphRect == Rectangle.Empty) + { + Size size = InheritanceGlyph.Size; + inheritanceGlyphRect = new Rectangle(0, 0, size.Width, size.Height); + } + return inheritanceGlyphRect; + } + } + + /// + /// Adds an inherited control to our list. This creates a tool tip for that control. + /// + public void AddInheritedControl(Control c, InheritanceLevel level) + { + if (tooltip == null) + { + tooltip = new ToolTip(); + tooltip.ShowAlways = true; + } + + Debug.Assert(level != InheritanceLevel.NotInherited, "This should only be called for inherited components."); + string text; + if (level == InheritanceLevel.InheritedReadOnly) + { + text = SR.DesignerInheritedReadOnly; + } + else + { + text = SR.DesignerInherited; + } + + tooltip.SetToolTip(c, text); + foreach (Control child in c.Controls) + { + if (child.Site == null) + { + tooltip.SetToolTip(child, text); + } + } + } + + public void Dispose() + { + if (tooltip != null) + { + tooltip.Dispose(); + } + } + + /// + /// Removes a previously added inherited control. + /// + public void RemoveInheritedControl(Control c) + { + if (tooltip != null && tooltip.GetToolTip(c).Length > 0) + { + tooltip.SetToolTip(c, null); + foreach (Control child in c.Controls) + { + if (child.Site == null) + { + tooltip.SetToolTip(child, null); + } + } + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/NoBorderRenderer.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/NoBorderRenderer.cs new file mode 100644 index 00000000000..a0bba770d6e --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/NoBorderRenderer.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + internal class NoBorderRenderer : ToolStripProfessionalRenderer + { + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) + { + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ParentControlDesigner.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ParentControlDesigner.cs index f26fa746e54..d6a82d98650 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ParentControlDesigner.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ParentControlDesigner.cs @@ -4,6 +4,8 @@ using System.Collections; using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Design; @@ -18,6 +20,20 @@ namespace System.Windows.Forms.Design /// public class ParentControlDesigner : ControlDesigner, IOleDragClient { + private IComponentChangeService componentChangeSvc; + + private Control pendingRemoveControl; // we've gotten an OnComponentRemoving, and are waiting for OnComponentRemove + + private bool checkSnapLineSetting = true; // Since layout options is global for the duration of the designer, we should only query it once. + private bool defaultUseSnapLines = false; + private bool gridSnap = true; + private bool getDefaultGridSnap = true; + private bool parentCanSetGridSnap = true; // been set explicitly by a user - so to ignore the parent's setting + + private StatusCommandUI statusCommandUI; // UI for setting the StatusBar Information.. + + private int suspendChanging = 0; + /// /// This is called after the user selects a toolbox item (that has a ParentControlDesigner /// associated with it) and draws a reversible rectangle on a designer's surface. If @@ -252,7 +268,186 @@ public override GlyphCollection GetGlyphs(GlyphSelectionType selectionType) /// protected Rectangle GetUpdatedRect(Rectangle originalRect, Rectangle dragRect, bool updateSize) { - throw new NotImplementedException(SR.NotImplementedByDesign); + Rectangle updatedRect = Rectangle.Empty;//the rectangle with updated coords that we will return + + if (SnapToGrid) + { + Size gridSize = GridSize; + Point halfGrid = new Point(gridSize.Width / 2, gridSize.Height / 2); + + updatedRect = dragRect; + updatedRect.X = originalRect.X; + updatedRect.Y = originalRect.Y; + + // decide to snap the start location to grid ... + if (dragRect.X != originalRect.X) + { + updatedRect.X = (dragRect.X / gridSize.Width) * gridSize.Width; + + // Snap the location to the grid point closest to the dragRect location + if (dragRect.X - updatedRect.X > halfGrid.X) + { + updatedRect.X += gridSize.Width; + } + } + + if (dragRect.Y != originalRect.Y) + { + updatedRect.Y = (dragRect.Y / gridSize.Height) * gridSize.Height; + + // Snap the location to the grid point closest to the dragRect location + if (dragRect.Y - updatedRect.Y > halfGrid.Y) + { + updatedRect.Y += gridSize.Height; + } + } + + // here, we need to calculate the new size depending on how we snap to the grid ... + if (updateSize) + { + // update the width and the height + updatedRect.Width = ((dragRect.X + dragRect.Width) / gridSize.Width) * gridSize.Width - updatedRect.X; + updatedRect.Height = ((dragRect.Y + dragRect.Height) / gridSize.Height) * gridSize.Height - updatedRect.Y; + + // ASURT 71552 Added so that if the updated dimnesion is smaller than grid dimension then snap that dimension to the grid dimension + if (updatedRect.Width < gridSize.Width) + updatedRect.Width = gridSize.Width; + if (updatedRect.Height < gridSize.Height) + updatedRect.Height = gridSize.Height; + } + } + else + { + updatedRect = dragRect; + } + + return updatedRect; + } + + private bool DefaultUseSnapLines + { + get + { + if (checkSnapLineSetting) + { + checkSnapLineSetting = false; + defaultUseSnapLines = DesignerUtils.UseSnapLines(Component.Site); + } + return defaultUseSnapLines; + } + } + + private ParentControlDesigner GetParentControlDesignerOfParent() + { + Control parent = Control.Parent; + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (parent != null && host != null) + { + return (host.GetDesigner(parent) as ParentControlDesigner); + } + return null; + } + + private IServiceProvider ServiceProvider + { + get + { + if (Component != null) + { + return Component.Site; + } + + return null; + } + } + + private bool SnapToGrid + { + get + { + // If snaplines are on, the we never want to snap to grid + if (DefaultUseSnapLines) + { + return false; + } + else if (getDefaultGridSnap) + { + gridSnap = true; + //Before we check our options page, we need to see if our parent is a ParentControlDesigner, is so, then we will want to inherit all our grid/snap setting from it - instead of our options page + ParentControlDesigner parent = GetParentControlDesignerOfParent(); + if (parent != null) + { + gridSnap = parent.SnapToGrid; + } + else + { + object optionValue = DesignerUtils.GetOptionValue(ServiceProvider, "SnapToGrid"); + if (optionValue != null && optionValue is bool) + { + gridSnap = (bool)optionValue; + } + } + } + return gridSnap; + } + set + { + if (gridSnap != value) + { + if (parentCanSetGridSnap) + { + parentCanSetGridSnap = false; + } + + if (getDefaultGridSnap) + { + getDefaultGridSnap = false; + } + gridSnap = value; + + //now, notify all child parent control designers that we have changed our setting 'cause they might to change along with us, unless the user has explicitly set those values... + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + foreach (Control child in Control.Controls) + { + if (host.GetDesigner(child) is ParentControlDesigner designer) + { + designer.GridSnapOfParentChanged(gridSnap); + } + } + } + + } + } + } + + private void GridSnapOfParentChanged(bool gridSnap) + { + if (parentCanSetGridSnap) + { + // If the parent sets us, then treat this as if no one set us + bool getDefaultGridSnapTemp = getDefaultGridSnap; + SnapToGrid = gridSnap; + parentCanSetGridSnap = true; + getDefaultGridSnap = getDefaultGridSnapTemp; + } + } + + internal void SuspendChangingEvents() + { + suspendChanging++; + Debug.Assert(suspendChanging > 0, "Unbalanced SuspendChangingEvents\\ResumeChangingEvents"); + } + + internal void ForceComponentChanging() + { + componentChangeSvc.OnComponentChanging(Control, TypeDescriptor.GetProperties(Control)["Controls"]); + } + internal void ResumeChangingEvents() + { + suspendChanging--; + Debug.Assert(suspendChanging >= 0, "Unbalanced SuspendChangingEvents\\ResumeChangingEvents"); } /// @@ -261,7 +456,49 @@ protected Rectangle GetUpdatedRect(Rectangle originalRect, Rectangle dragRect, b /// public override void Initialize(IComponent component) { - throw new NotImplementedException(SR.NotImplementedByDesign); + base.Initialize(component); + if (Control is ScrollableControl) + { + ((ScrollableControl)Control).Scroll += new ScrollEventHandler(this.OnScroll); + } + EnableDragDrop(true); + // Hook load events. At the end of load, we need to do a scan through all of our child controls to see which ones are being inherited. We connect these up. + IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); + if (host != null) + { + componentChangeSvc = (IComponentChangeService)host.GetService(typeof(IComponentChangeService)); + if (componentChangeSvc != null) + { + componentChangeSvc.ComponentRemoving += new ComponentEventHandler(OnComponentRemoving); + componentChangeSvc.ComponentRemoved += new ComponentEventHandler(OnComponentRemoved); + } + } + // update the Status Command + statusCommandUI = new StatusCommandUI(component.Site); + } + + private void OnComponentRemoving(object sender, ComponentEventArgs e) + { + using (Control comp = e.Component as Control) + { + if (comp != null && comp.Parent != null && comp.Parent == Control) + { + pendingRemoveControl = (Control)comp; + //We suspend Component Changing Events for bulk operations to avoid unnecessary serialization\deserialization for undo see bug 488115 + if (suspendChanging == 0) + { + componentChangeSvc.OnComponentChanging(Control, TypeDescriptor.GetProperties(Control)["Controls"]); + } + } + } + } + private void OnComponentRemoved(object sender, ComponentEventArgs e) + { + if (e.Component == pendingRemoveControl) + { + pendingRemoveControl = null; + componentChangeSvc.OnComponentChanged(Control, TypeDescriptor.GetProperties(Control)["Controls"], null, null); + } } /// @@ -385,5 +622,11 @@ protected override void PreFilterProperties(IDictionary properties) { throw new NotImplementedException(SR.NotImplementedByDesign); } + + internal Point GetSnappedPoint(Point pt) + { + Rectangle r = GetUpdatedRect(Rectangle.Empty, new Rectangle(pt.X, pt.Y, 0, 0), false); + return new Point(r.X, r.Y); + } } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionRules.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionRules.cs index 1e7529dc601..c3b904b41ff 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionRules.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionRules.cs @@ -7,10 +7,7 @@ namespace System.Windows.Forms.Design { /// - /// - /// Specifies a set of selection rule identifiers that - /// can be used to indicate attributes for a selected component. - /// + /// Specifies a set of selection rule identifiers that can be used to indicate attributes for a selected component. /// // We need to combine the SelectionRules. [SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags")] @@ -18,92 +15,47 @@ namespace System.Windows.Forms.Design public enum SelectionRules { /// - /// - /// Indicates - /// no special selection attributes. - /// + /// Indicates no special selection attributes. /// None = 0x00000000, /// - /// - /// Indicates - /// the given component supports a location - /// property that allows it to be moved on the screen, and - /// that the selection service is not currently locked. - /// + /// Indicates the given component supports a location property that allows it to be moved on the screen, and that the selection service is not currently locked. /// Moveable = 0x10000000, /// - /// - /// Indicates - /// the given component has some form of visible user - /// interface and the selection service is drawing a selection border around - /// this user interface. If a selected component has this rule set, you can assume - /// that the component implements - /// and that it - /// is associated with a corresponding design instance. - /// + /// Indicates the given component has some form of visible user interface and the selection service is drawing a selection border around this user interface. If a selected component has this rule set, you can assume that the component implements and that it is associated with a corresponding design instance. /// Visible = 0x40000000, /// - /// - /// Indicates - /// the given component is locked to - /// its container. Overrides the moveable and sizeable - /// properties of this enum. - /// + /// Indicates the given component is locked to its container. Overrides the moveable and sizeable properties of this enum. /// Locked = unchecked((int)0x80000000), /// - /// - /// Indicates - /// the given component supports resize from - /// the top. This bit will be ignored unless the Sizeable - /// bit is also set. - /// + /// Indicates the given component supports resize from the top. This bit will be ignored unless the Sizeable bit is also set. /// TopSizeable = 0x00000001, /// - /// - /// Indicates - /// the given component supports resize from - /// the bottom. This bit will be ignored unless the Sizeable - /// bit is also set. - /// + /// Indicates the given component supports resize from the bottom. This bit will be ignored unless the Sizeable bit is also set. /// BottomSizeable = 0x00000002, /// - /// - /// Indicates - /// the given component supports resize from - /// the left. This bit will be ignored unless the Sizeable - /// bit is also set. - /// + /// Indicates the given component supports resize from the left. This bit will be ignored unless the Sizeable bit is also set. /// LeftSizeable = 0x00000004, /// - /// - /// Indicates - /// the given component supports resize from - /// the right. This bit will be ignored unless the Sizeable - /// bit is also set. - /// + /// Indicates the given component supports resize from the right. This bit will be ignored unless the Sizeable bit is also set. /// RightSizeable = 0x00000008, /// - /// - /// Indicates - /// the given component supports sizing - /// in all directions, and the selection service is not currently locked. - /// + /// Indicates the given component supports sizing in all directions, and the selection service is not currently locked. /// AllSizeable = TopSizeable | BottomSizeable | LeftSizeable | RightSizeable } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionStyles.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionStyles.cs new file mode 100644 index 00000000000..1687e87abcb --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionStyles.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Design +{ + /// + /// Specifies identifiers to use to indicate the style of the selection frame of a component. + /// + [Flags] + internal enum SelectionStyles + { + /// + /// The component is not currently selected. + /// + None = 0, + + /// + /// A component is selected and may be dragged around + /// + Selected = 0x01, + + /// + /// An alternative selection border, indicating that a component is in active editing mode and that clicking and dragging on the component affects the component itself not its position in the designer. + /// + Active = 0x02, + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionUIService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionUIService.cs new file mode 100644 index 00000000000..f1ac31dbd9d --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectionUIService.cs @@ -0,0 +1,1966 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using Microsoft.Win32; + +namespace System.Windows.Forms.Design +{ + /// + /// The selection manager handles selection within a form. There is one selection manager for each form or top level designer. + /// A selection consists of an array of components. One component is designated the "primary" selection and is displayed with different grab handles. + /// An individual selection may or may not have UI associated with it. If the selection manager can find a suitable designer that is representing the selection, it will highlight the designer's border. If the merged property set has a location property, the selection's rules will allow movement. Also, if the property set has a size property, the selection's rules will allow for sizing. Grab handles may be drawn around the designer and user interactions involving the selection frame and grab handles are initiated here, but the actual movement of the objects is done in a designer object that implements the ISelectionHandler interface. + /// + internal sealed class SelectionUIService : Control, ISelectionUIService + { + private static readonly Point s_invalidPoint = new Point(int.MinValue, int.MinValue); + private const int HITTEST_CONTAINER_SELECTOR = 0x0001; + private const int HITTEST_NORMAL_SELECTION = 0x0002; + private const int HITTEST_DEFAULT = HITTEST_CONTAINER_SELECTOR | HITTEST_NORMAL_SELECTION; + // These are used during a drag operation, either through our own handle drag or through ISelectionUIService + private ISelectionUIHandler _dragHandler; // the current drag handler + private object[] _dragComponents; // the controls being dragged + private SelectionRules _dragRules; // movement constraints for the drag + private bool _dragMoved = false; + private object _containerDrag; // object being dragged during a container drag + + // These are used during a drag of a selection grab handle + private bool _ignoreCaptureChanged = false; + private int _mouseDragHitTest; // where the hit occurred that caused the drag + private Point _mouseDragAnchor = s_invalidPoint; // anchor point of the drag + private Rectangle _mouseDragOffset = Rectangle.Empty; // current drag offset + private Point _lastMoveScreenCoord = Point.Empty; + private bool _ctrlSelect = false; // was the CTRL key down when the drag began + private bool _mouseDragging = false; // Are we actually doing a drag? + + private ContainerSelectorActiveEventHandler _containerSelectorActive; // the event we fire when user interacts with container selector + private Hashtable _selectionItems; + private readonly Hashtable _selectionHandlers; // Component UI handlers + + private bool _savedVisible; // we stash this when we mess with visibility ourselves. + private bool _batchMode; + private bool _batchChanged; + private bool _batchSync; + private readonly ISelectionService _selSvc; + private readonly IDesignerHost _host; + private DesignerTransaction _dragTransaction; + + /// + /// Creates a new selection manager object. The selection manager manages all selection of all designers under the current form file. + /// + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] + public SelectionUIService(IDesignerHost host) : base() + { + SetStyle(ControlStyles.StandardClick | ControlStyles.Opaque | ControlStyles.OptimizedDoubleBuffer, true); + _host = host; + _dragHandler = null; + _dragComponents = null; + _selectionItems = new Hashtable(); + _selectionHandlers = new Hashtable(); + AllowDrop = true; + + // Not really any reason for this, except that it can be handy when using Spy++ + Text = "SelectionUIOverlay"; + _selSvc = (ISelectionService)host.GetService(typeof(ISelectionService)); + if (_selSvc != null) + { + _selSvc.SelectionChanged += new EventHandler(OnSelectionChanged); + } + + // And configure the events we want to listen to. + host.TransactionOpened += new EventHandler(OnTransactionOpened); + host.TransactionClosed += new DesignerTransactionCloseEventHandler(OnTransactionClosed); + if (host.InTransaction) + { + OnTransactionOpened(host, EventArgs.Empty); + } + + // Listen to the SystemEvents so that we can resync selection based on display settings etc. + SystemEvents.DisplaySettingsChanged += new EventHandler(OnSystemSettingChanged); + SystemEvents.InstalledFontsChanged += new EventHandler(OnSystemSettingChanged); + SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + + /// + /// override of control. + /// + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.Style &= ~(NativeMethods.WS_CLIPSIBLINGS | NativeMethods.WS_CLIPCHILDREN); + return cp; + } + } + + /// + /// Called to initiate a mouse drag on the selection overlay. We cache some state here. + /// + private void BeginMouseDrag(Point anchor, int hitTest) + { + Capture = true; + _ignoreCaptureChanged = false; + _mouseDragAnchor = anchor; + _mouseDragging = true; + _mouseDragHitTest = hitTest; + _mouseDragOffset = new Rectangle(); + _savedVisible = Visible; + } + + /// + /// Displays the given exception to the user. + /// + private void DisplayError(Exception e) + { + IUIService uis = (IUIService)_host.GetService(typeof(IUIService)); + if (uis != null) + { + uis.ShowError(e); + } + else + { + string message = e.Message; + if (message == null || message.Length == 0) + { + message = e.ToString(); + } + RTLAwareMessageBox.Show(null, message, null, MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, 0); + } + } + + /// + /// Disposes the entire selection UI manager. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_selSvc != null) + { + _selSvc.SelectionChanged -= new EventHandler(OnSelectionChanged); + } + if (_host != null) + { + _host.TransactionOpened -= new EventHandler(OnTransactionOpened); + _host.TransactionClosed -= new DesignerTransactionCloseEventHandler(OnTransactionClosed); + if (_host.InTransaction) + { + OnTransactionClosed(_host, new DesignerTransactionCloseEventArgs(true, true)); + } + } + foreach (SelectionUIItem s in _selectionItems.Values) + { + s.Dispose(); + } + _selectionHandlers.Clear(); + _selectionItems.Clear(); + // Listen to the SystemEvents so that we can resync selection based on display settings etc. + SystemEvents.DisplaySettingsChanged -= new EventHandler(OnSystemSettingChanged); + SystemEvents.InstalledFontsChanged -= new EventHandler(OnSystemSettingChanged); + SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + base.Dispose(disposing); + } + + /// + /// Called when we want to finish a mouse drag and clean up our variables. We call this from multiple places, depending on the state of the finish. This does NOT end the drag -- for that must call EndDrag. This just cleans up the state of the mouse. + /// + private void EndMouseDrag(Point position) + { + // it's possible for us to be destroyed in a drag -- e.g. if this is the tray's selectionuiservice and the last item is dragged out, so check diposed first + if (IsDisposed) + { + return; + } + + _ignoreCaptureChanged = true; + Capture = false; + _mouseDragAnchor = s_invalidPoint; + _mouseDragOffset = Rectangle.Empty; + _mouseDragHitTest = 0; + _dragMoved = false; + SetSelectionCursor(position); + _mouseDragging = _ctrlSelect = false; + } + + /// + /// Determines the selection hit test at the given point. The point should be in screen coordinates. + /// + private HitTestInfo GetHitTest(Point value, int flags) + { + Point pt = PointToClient(value); + foreach (SelectionUIItem item in _selectionItems.Values) + { + if ((flags & HITTEST_CONTAINER_SELECTOR) != 0) + { + if (item is ContainerSelectionUIItem && (item.GetRules() & SelectionRules.Visible) != SelectionRules.None) + { + int hitTest = item.GetHitTest(pt); + if ((hitTest & SelectionUIItem.CONTAINER_SELECTOR) != 0) + { + return new HitTestInfo(hitTest, item, true); + } + } + } + + if ((flags & HITTEST_NORMAL_SELECTION) != 0) + { + if (!(item is ContainerSelectionUIItem) && (item.GetRules() & SelectionRules.Visible) != SelectionRules.None) + { + int hitTest = item.GetHitTest(pt); + if (hitTest != SelectionUIItem.NOHIT) + { + if (hitTest != 0) + { + return new HitTestInfo(hitTest, item); + } + else + { + return new HitTestInfo(SelectionUIItem.NOHIT, item); + } + } + } + } + } + return new HitTestInfo(SelectionUIItem.NOHIT, null); + } + + private ISelectionUIHandler GetHandler(object component) => (ISelectionUIHandler)_selectionHandlers[component]; + + /// + /// This method returns a well-formed name for a drag transaction based on the rules it is given. + /// + public static string GetTransactionName(SelectionRules rules, object[] objects) + { + // Determine a nice name for the drag operation + string transactionName; + if ((int)(rules & SelectionRules.Moveable) != 0) + { + if (objects.Length > 1) + { + transactionName = string.Format(SR.DragDropMoveComponents, objects.Length); + } + else + { + string name = string.Empty; + if (objects.Length > 0) + { + if (objects[0] is IComponent comp && comp.Site != null) + { + name = comp.Site.Name; + } + else + { + name = objects[0].GetType().Name; + } + } + transactionName = string.Format(SR.DragDropMoveComponent, name); + } + } + else if ((int)(rules & SelectionRules.AllSizeable) != 0) + { + if (objects.Length > 1) + { + transactionName = string.Format(SR.DragDropSizeComponents, objects.Length); + } + else + { + string name = string.Empty; + if (objects.Length > 0) + { + if (objects[0] is IComponent comp && comp.Site != null) + { + name = comp.Site.Name; + } + else + { + name = objects[0].GetType().Name; + } + } + transactionName = string.Format(SR.DragDropSizeComponent, name); + } + } + else + { + transactionName = string.Format(SR.DragDropDragComponents, objects.Length); + } + + return transactionName; + } + + /// + /// Called by the designer host when it is entering or leaving a batch operation. Here we queue up selection notification and we turn off our UI. + /// + private void OnTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) + { + if (e.LastTransaction) + { + _batchMode = false; + if (_batchChanged) + { + _batchChanged = false; + ((ISelectionUIService)this).SyncSelection(); + } + if (_batchSync) + { + _batchSync = false; + ((ISelectionUIService)this).SyncComponent(null); + } + } + } + + /// + /// Called by the designer host when it is entering or leaving a batch operation. Here we queue up selection notification and we turn offour UI. + /// + private void OnTransactionOpened(object sender, EventArgs e) + { + _batchMode = true; + } + + /// + /// update our window region on first create. We shouldn't do this before the handle is created or else we will force creation. + /// + protected override void OnHandleCreated(EventArgs e) + { + Debug.Assert(!RecreatingHandle, "Perf hit: we are recreating the docwin handle"); + base.OnHandleCreated(e); + // Default the shape of the control to be empty, so that if nothing is initially selected that our window surface doesn't interfere. + UpdateWindowRegion(); + } + + /// + /// Called whenever a component changes. Here we update our selection information so that the selection rectangles are all up to date. + /// + private void OnComponentChanged(object sender, ComponentChangedEventArgs ccevent) + { + if (!_batchMode) + { + ((ISelectionUIService)this).SyncSelection(); + } + else + { + _batchChanged = true; + } + } + + /// + /// called by the formcore when someone has removed a component. This will remove any selection on the component without disturbing the rest of the selection + /// + private void OnComponentRemove(object sender, ComponentEventArgs ce) + { + _selectionHandlers.Remove(ce.Component); + _selectionItems.Remove(ce.Component); + ((ISelectionUIService)this).SyncComponent(ce.Component); + } + + /// + /// Called to invoke the container active event, if a designer has bound to it. + /// + private void OnContainerSelectorActive(ContainerSelectorActiveEventArgs e) + { + _containerSelectorActive?.Invoke(this, e); + } + + /// + /// Called when the selection changes. We sync up the UI with the selection at this point. + /// + private void OnSelectionChanged(object sender, EventArgs e) + { + ICollection selection = _selSvc.GetSelectedComponents(); + Hashtable newSelection = new Hashtable(selection.Count); + bool shapeChanged = false; + foreach (object comp in selection) + { + object existingItem = _selectionItems[comp]; + bool create = true; + if (existingItem != null) + { + if (existingItem is ContainerSelectionUIItem item) + { + item.Dispose(); + shapeChanged = true; + } + else + { + newSelection[comp] = existingItem; + create = false; + } + } + + if (create) + { + shapeChanged = true; + newSelection[comp] = new SelectionUIItem(this, comp); + } + } + + if (!shapeChanged) + { + shapeChanged = _selectionItems.Keys.Count != newSelection.Keys.Count; + } + + _selectionItems = newSelection; + + if (shapeChanged) + { + UpdateWindowRegion(); + } + Invalidate(); + Update(); + } + + /// + /// User setting requires that we repaint. + /// + private void OnSystemSettingChanged(object sender, EventArgs e) => Invalidate(); + + /// + /// User setting requires that we repaint. + /// + private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) => Invalidate(); + + /// + /// Inheriting classes should override this method to handle this event. Call super.onDragEnter to send this event to any registered event listeners. + /// + protected override void OnDragEnter(DragEventArgs devent) + { + base.OnDragEnter(devent); + if (_dragHandler != null) + { + _dragHandler.OleDragEnter(devent); + } + } + + /// + /// Inheriting classes should override this method to handle this event. Call super.onDragOver to send this event to any registered event listeners. + /// + protected override void OnDragOver(DragEventArgs devent) + { + base.OnDragOver(devent); + if (_dragHandler != null) + { + _dragHandler.OleDragOver(devent); + } + } + /// + /// Inheriting classes should override this method to handle this event. Call super.onDragLeave to send this event to any registered event listeners. + /// + protected override void OnDragLeave(EventArgs e) + { + base.OnDragLeave(e); + if (_dragHandler != null) + { + _dragHandler.OleDragLeave(); + } + } + + /// + /// Inheriting classes should override this method to handle this event. Call super.onDragDrop to send this event to any registered event listeners. + /// + protected override void OnDragDrop(DragEventArgs devent) + { + base.OnDragDrop(devent); + if (_dragHandler != null) + { + _dragHandler.OleDragDrop(devent); + } + } + + /// + /// Inheriting classes should override this method to handle this event. Call base.OnDoiubleClick to send this event to any registered event listeners. + /// + protected override void OnDoubleClick(EventArgs devent) + { + base.OnDoubleClick(devent); + if (_selSvc != null) + { + object selComp = _selSvc.PrimarySelection; + Debug.Assert(selComp != null, "Illegal selection on double-click"); + if (selComp != null) + { + ISelectionUIHandler handler = GetHandler(selComp); + if (handler != null) + { + handler.OnSelectionDoubleClick((IComponent)selComp); + } + } + } + } + + /// + /// Overrides Control to handle our selection grab handles. + /// + // Standard 'catch all - rethrow critical' exception pattern + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + protected override void OnMouseDown(MouseEventArgs me) + { + if (_dragHandler == null && _selSvc != null) + { + try + { + // First, did the user step on anything? + Point anchor = PointToScreen(new Point(me.X, me.Y)); + HitTestInfo hti = GetHitTest(anchor, HITTEST_DEFAULT); + int hitTest = hti.hitTest; + if ((hitTest & SelectionUIItem.CONTAINER_SELECTOR) != 0) + { + _selSvc.SetSelectedComponents(new object[] { hti.selectionUIHit._component }, SelectionTypes.Auto); + // Then do a drag... + SelectionRules rules = SelectionRules.Moveable; + if (((ISelectionUIService)this).BeginDrag(rules, anchor.X, anchor.Y)) + { + Visible = false; + _containerDrag = hti.selectionUIHit._component; + BeginMouseDrag(anchor, hitTest); + } + } + else if (hitTest != SelectionUIItem.NOHIT && me.Button == MouseButtons.Left) + { + SelectionRules rules = SelectionRules.None; + // If the CTRL key isn't down, select this component, otherwise, we wait until the mouse up. Make sure the component is selected + _ctrlSelect = (Control.ModifierKeys & Keys.Control) != Keys.None; + if (!_ctrlSelect) + { + _selSvc.SetSelectedComponents(new object[] { hti.selectionUIHit._component }, SelectionTypes.Primary); + } + + if ((hitTest & SelectionUIItem.MOVE_MASK) != 0) + { + rules |= SelectionRules.Moveable; + } + if ((hitTest & SelectionUIItem.SIZE_MASK) != 0) + { + if ((hitTest & (SelectionUIItem.SIZE_X | SelectionUIItem.POS_RIGHT)) == (SelectionUIItem.SIZE_X | SelectionUIItem.POS_RIGHT)) + { + rules |= SelectionRules.RightSizeable; + } + if ((hitTest & (SelectionUIItem.SIZE_X | SelectionUIItem.POS_LEFT)) == (SelectionUIItem.SIZE_X | SelectionUIItem.POS_LEFT)) + { + rules |= SelectionRules.LeftSizeable; + } + if ((hitTest & (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_TOP)) == (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_TOP)) + { + rules |= SelectionRules.TopSizeable; + } + if ((hitTest & (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_BOTTOM)) == (SelectionUIItem.SIZE_Y | SelectionUIItem.POS_BOTTOM)) + { + rules |= SelectionRules.BottomSizeable; + } + if (((ISelectionUIService)this).BeginDrag(rules, anchor.X, anchor.Y)) + { + BeginMouseDrag(anchor, hitTest); + } + } + else + { + // Our mouse is in drag mode. We defer the actual move until the user moves the mouse. + _dragRules = rules; + BeginMouseDrag(anchor, hitTest); + } + } + else if (hitTest == SelectionUIItem.NOHIT) + { + _dragRules = SelectionRules.None; + _mouseDragAnchor = s_invalidPoint; + return; + } + } + catch (Exception e) + { + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + else if (e != CheckoutException.Canceled) + { + DisplayError(e); + } + } + } + } + + /// + /// Overrides Control to handle our selection grab handles. + /// + protected override void OnMouseMove(MouseEventArgs me) + { + base.OnMouseMove(me); + Point screenCoord = PointToScreen(new Point(me.X, me.Y)); + HitTestInfo hti = GetHitTest(screenCoord, HITTEST_CONTAINER_SELECTOR); + int hitTest = hti.hitTest; + if (hitTest != SelectionUIItem.CONTAINER_SELECTOR && hti.selectionUIHit != null) + { + OnContainerSelectorActive(new ContainerSelectorActiveEventArgs(hti.selectionUIHit._component)); + } + if (_lastMoveScreenCoord == screenCoord) + { + return; + } + + // If we're not dragging then set the cursor correctly. + if (!_mouseDragging) + { + SetSelectionCursor(screenCoord); + } + else + { + // we have to make sure the mouse moved farther than the minimum drag distance before we actually start the drag + if (!((ISelectionUIService)this).Dragging && (_mouseDragHitTest & SelectionUIItem.MOVE_MASK) != 0) + { + Size minDragSize = SystemInformation.DragSize; + if ( + Math.Abs(screenCoord.X - _mouseDragAnchor.X) < minDragSize.Width && + Math.Abs(screenCoord.Y - _mouseDragAnchor.Y) < minDragSize.Height) + { + return; + } + else + { + _ignoreCaptureChanged = true; + if (((ISelectionUIService)this).BeginDrag(_dragRules, _mouseDragAnchor.X, _mouseDragAnchor.Y)) + { + // we're moving, so we don't care about the ctrl key any more + _ctrlSelect = false; + } + else + { + EndMouseDrag(MousePosition); + return; + } + } + } + + Rectangle old = _mouseDragOffset; + if ((_mouseDragHitTest & SelectionUIItem.MOVE_X) != 0) + { + _mouseDragOffset.X = screenCoord.X - _mouseDragAnchor.X; + } + if ((_mouseDragHitTest & SelectionUIItem.MOVE_Y) != 0) + { + _mouseDragOffset.Y = screenCoord.Y - _mouseDragAnchor.Y; + } + if ((_mouseDragHitTest & SelectionUIItem.SIZE_X) != 0) + { + if ((_mouseDragHitTest & SelectionUIItem.POS_LEFT) != 0) + { + _mouseDragOffset.X = screenCoord.X - _mouseDragAnchor.X; + _mouseDragOffset.Width = _mouseDragAnchor.X - screenCoord.X; + } + else + { + _mouseDragOffset.Width = screenCoord.X - _mouseDragAnchor.X; + } + } + if ((_mouseDragHitTest & SelectionUIItem.SIZE_Y) != 0) + { + if ((_mouseDragHitTest & SelectionUIItem.POS_TOP) != 0) + { + _mouseDragOffset.Y = screenCoord.Y - _mouseDragAnchor.Y; + _mouseDragOffset.Height = _mouseDragAnchor.Y - screenCoord.Y; + } + else + { + _mouseDragOffset.Height = screenCoord.Y - _mouseDragAnchor.Y; + } + } + + if (!old.Equals(_mouseDragOffset)) + { + Rectangle delta = _mouseDragOffset; + delta.X -= old.X; + delta.Y -= old.Y; + delta.Width -= old.Width; + delta.Height -= old.Height; + + if (delta.X != 0 || delta.Y != 0 || delta.Width != 0 || delta.Height != 0) + { + // Go to default cursor for moves... + if ((_mouseDragHitTest & SelectionUIItem.MOVE_X) != 0 + || (_mouseDragHitTest & SelectionUIItem.MOVE_Y) != 0) + { + Cursor = Cursors.Default; + } + ((ISelectionUIService)this).DragMoved(delta); + } + } + } + } + + /// + /// Overrides Control to handle our selection grab handles. + /// + // Standard 'catch all - rethrow critical' exception pattern + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + protected override void OnMouseUp(MouseEventArgs me) + { + try + { + Point screenCoord = PointToScreen(new Point(me.X, me.Y)); + if (_ctrlSelect && !_mouseDragging && _selSvc != null) + { + HitTestInfo hti = GetHitTest(screenCoord, HITTEST_DEFAULT); + _selSvc.SetSelectedComponents(new object[] { hti.selectionUIHit._component }, SelectionTypes.Primary); + } + if (_mouseDragging) + { + object oldContainerDrag = _containerDrag; + bool oldDragMoved = _dragMoved; + EndMouseDrag(screenCoord); + if (((ISelectionUIService)this).Dragging) + { + ((ISelectionUIService)this).EndDrag(false); + } + if (me.Button == MouseButtons.Right && oldContainerDrag != null && !oldDragMoved) + { + OnContainerSelectorActive(new ContainerSelectorActiveEventArgs(oldContainerDrag, ContainerSelectorActiveEventArgsType.Contextmenu)); + } + } + } + catch (Exception e) + { + if (ClientUtils.IsCriticalException(e)) + { + throw; + } + else if (e != CheckoutException.Canceled) + { + DisplayError(e); + } + } + } + + /// + /// If the selection manager move, this indicates that the form has autoscolling enabled and has been scrolled. We have to invalidate here because we may get moved before the rest of the components so we may draw the selection in the wrong spot. + /// + protected override void OnMove(EventArgs e) + { + base.OnMove(e); + Invalidate(); + } + + /// + /// overrides control.onPaint. here we paint the selection handles. The window's region was setup earlier. + /// + protected override void OnPaint(PaintEventArgs e) + { + // Paint the regular selection items first, and then the container selectors last so they draw over the top. + foreach (SelectionUIItem item in _selectionItems.Values) + { + if (item is ContainerSelectionUIItem) + { + continue; + } + item.DoPaint(e.Graphics); + } + + foreach (SelectionUIItem item in _selectionItems.Values) + { + if (item is ContainerSelectionUIItem) + { + item.DoPaint(e.Graphics); + } + } + } + + /// + /// Sets the appropriate selection cursor at the given point. + /// + private void SetSelectionCursor(Point pt) + { + Point clientCoords = PointToClient(pt); + // We render the cursor in the same order we paint. + foreach (SelectionUIItem item in _selectionItems.Values) + { + if (item is ContainerSelectionUIItem) + { + continue; + } + Cursor cursor = item.GetCursorAtPoint(clientCoords); + if (cursor != null) + { + if (cursor == Cursors.Default) + { + Cursor = null; + } + else + { + Cursor = cursor; + } + return; + } + } + + foreach (SelectionUIItem item in _selectionItems.Values) + { + if (item is ContainerSelectionUIItem) + { + Cursor cursor = item.GetCursorAtPoint(clientCoords); + if (cursor != null) + { + if (cursor == Cursors.Default) + { + Cursor = null; + } + else + { + Cursor = cursor; + } + return; + } + } + } + // Don't know what to set; just use the default. + Cursor = null; + } + + /// + /// called when the overlay region is invalid and should be updated + /// + private void UpdateWindowRegion() + { + Region region = new Region(new Rectangle(0, 0, 0, 0)); + foreach (SelectionUIItem item in _selectionItems.Values) + { + region.Union(item.GetRegion()); + } + Region = region; + } + + /// + /// Override of our control's WNDPROC. We diddle with capture a bit, and it's important to turn this off if the capture changes. + /// + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + case NativeMethods.WM_LBUTTONUP: + case NativeMethods.WM_RBUTTONUP: + if (_mouseDragAnchor != s_invalidPoint) + { + _ignoreCaptureChanged = true; + } + break; + + case NativeMethods.WM_CAPTURECHANGED: + if (!_ignoreCaptureChanged && _mouseDragAnchor != s_invalidPoint) + { + EndMouseDrag(MousePosition); + if (((ISelectionUIService)this).Dragging) + { + ((ISelectionUIService)this).EndDrag(true); + } + } + _ignoreCaptureChanged = false; + break; + } + base.WndProc(ref m); + } + + /// + /// This can be used to determine if the user is in the middle of a drag operation. + /// + bool ISelectionUIService.Dragging + { + get => _dragHandler != null; + } + + /// + /// Determines if the selection UI is shown or not. + /// + bool ISelectionUIService.Visible + { + get => Visible; + set => Visible = value; + } + + /// + /// Adds an event handler to the ContainerSelectorActive event. This event is fired whenever the user interacts with the container selector in a manor that would indicate that the selector should continued to be displayed. Since the container selector normally will vanish after a timeout, designers should listen to this event and reset the timeout when this event occurs. + /// + event ContainerSelectorActiveEventHandler ISelectionUIService.ContainerSelectorActive + { + add + { + _containerSelectorActive += value; + } + remove + { + _containerSelectorActive -= value; + } + } + + /// + /// Assigns a selection UI handler to a given component. The handler will be called when the UI service needs information about the component. A single selection UI handler can be assigned to multiple components. + /// When multiple components are dragged, only a single handler may control the drag. Because of this, only components that are assigned the same handler as the primary selection are included in drag operations. A selection UI handler is automatically unassigned when the component is removed from the container or disposed. + /// + void ISelectionUIService.AssignSelectionUIHandler(object component, ISelectionUIHandler handler) + { + ISelectionUIHandler oldHandler = (ISelectionUIHandler)_selectionHandlers[component]; + if (oldHandler != null) + { + // ASURT #44582: The collection editors do not dispose objects from the collection before setting a new collection. This causes items that are common to the old and new collections to come through this code path again, causing the exception to fire. So, we check to see if the SelectionUIHandler is same, and bail out in that case. + if (handler == oldHandler) + { + return; + } + Debug.Fail("A component may have only one selection UI handler."); + throw new InvalidOperationException(); + } + _selectionHandlers[component] = handler; + // If this component is selected, create a new UI handler for it. + if (_selSvc != null && _selSvc.GetComponentSelected(component)) + { + SelectionUIItem item = new SelectionUIItem(this, component); + _selectionItems[component] = item; + UpdateWindowRegion(); + item.Invalidate(); + } + } + + void ISelectionUIService.ClearSelectionUIHandler(object component, ISelectionUIHandler handler) + { + ISelectionUIHandler oldHandler = (ISelectionUIHandler)_selectionHandlers[component]; + if (oldHandler == handler) + { + _selectionHandlers[component] = null; + } + } + + /// + /// This can be called by an outside party to begin a drag of the currently selected set of components. + /// + // Standard 'catch all - rethrow critical' exception pattern + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + bool ISelectionUIService.BeginDrag(SelectionRules rules, int initialX, int initialY) + { + if (_dragHandler != null) + { + Debug.Fail("Caller is starting a drag, but there is already one in progress -- we cannot nest these!"); + return false; + } + + if (rules == SelectionRules.None) + { + Debug.Fail("Caller is starting requesting a drag with no drag rules."); + return false; + } + + if (_selSvc == null) + { + return false; + } + + _savedVisible = Visible; + // First, get the list of controls + ICollection col = _selSvc.GetSelectedComponents(); + object[] objects = new object[col.Count]; + col.CopyTo(objects, 0); + objects = ((ISelectionUIService)this).FilterSelection(objects, rules); + if (objects.Length == 0) + { + return false; // nothing selected + } + + // We allow all components with the same UI handler as the primary selection to participate in the drag. + ISelectionUIHandler primaryHandler = null; + object primary = _selSvc.PrimarySelection; + if (primary != null) + { + primaryHandler = GetHandler(primary); + } + if (primaryHandler == null) + { + return false; // no UI handler for selection + } + + // Now within the given selection, add those items that have the same UI handler and that have the proper rule constraints. + ArrayList list = new ArrayList(); + for (int i = 0; i < objects.Length; i++) + { + if (GetHandler(objects[i]) == primaryHandler) + { + SelectionRules compRules = primaryHandler.GetComponentRules(objects[i]); + if ((compRules & rules) == rules) + { + list.Add(objects[i]); + } + } + } + if (list.Count == 0) + { + return false; // nothing matching the given constraints + } + objects = list.ToArray(); + bool dragging = false; + // We must setup state before calling QueryBeginDrag. It is possible that QueryBeginDrag will cancel a drag (if it places a modal dialog, for example), so we must have the drag data all setup before it cancels. Then, we will check again after QueryBeginDrag to see if a cancel happened. + _dragComponents = objects; + _dragRules = rules; + _dragHandler = primaryHandler; + + string transactionName = GetTransactionName(rules, objects); + _dragTransaction = _host.CreateTransaction(transactionName); + try + { + if (primaryHandler.QueryBeginDrag(objects, rules, initialX, initialY)) + { + if (_dragHandler != null) + { + try + { + dragging = primaryHandler.BeginDrag(objects, rules, initialX, initialY); + } + catch (Exception e) + { + Debug.Fail("Drag handler threw during BeginDrag -- bad handler!", e.ToString()); + dragging = false; + } + } + } + } + finally + { + if (!dragging) + { + _dragComponents = null; + _dragRules = 0; + _dragHandler = null; + // Always commit this -- BeginDrag returns false for our drags because it is a complete operation. + if (_dragTransaction != null) + { + _dragTransaction.Commit(); + _dragTransaction = null; + } + } + } + return dragging; + } + + /// + /// Called by an outside party to update drag information. This can only be called after a successful call to beginDrag. + /// + void ISelectionUIService.DragMoved(Rectangle offset) + { + Rectangle newOffset = Rectangle.Empty; + if (_dragHandler == null) + { + throw new Exception(SR.DesignerBeginDragNotCalled); + } + + Debug.Assert(_dragComponents != null, "We should have a set of drag controls here"); + if ((_dragRules & SelectionRules.Moveable) == SelectionRules.None && (_dragRules & (SelectionRules.TopSizeable | SelectionRules.LeftSizeable)) == SelectionRules.None) + { + newOffset = new Rectangle(0, 0, offset.Width, offset.Height); + } + if ((_dragRules & SelectionRules.AllSizeable) == SelectionRules.None) + { + if (newOffset.IsEmpty) + { + newOffset = new Rectangle(offset.X, offset.Y, 0, 0); + } + else + { + newOffset.Width = newOffset.Height = 0; + } + } + + if (!newOffset.IsEmpty) + { + offset = newOffset; + } + + Visible = false; + _dragMoved = true; + _dragHandler.DragMoved(_dragComponents, offset); + } + + /// + /// Called by an outside party to finish a drag operation. This can only be called after a successful call to beginDrag. + /// + // Standard 'catch all - rethrow critical' exception pattern + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + void ISelectionUIService.EndDrag(bool cancel) + { + _containerDrag = null; + ISelectionUIHandler handler = _dragHandler; + object[] components = _dragComponents; + + // Clean these out so that even if we throw an exception we don't die. + _dragHandler = null; + _dragComponents = null; + _dragRules = SelectionRules.None; + if (handler == null) + { + throw new InvalidOperationException(); + } + + // Typically, the handler will be changing a bunch of component properties here. Optimize this by enclosing it within a batch call. + DesignerTransaction trans = null; + try + { + + IComponent comp = components[0] as IComponent; + if (components.Length > 1 || (components.Length == 1 && comp != null && comp.Site == null)) + { + trans = _host.CreateTransaction(string.Format(SR.DragDropMoveComponents, components.Length)); + } + else if (components.Length == 1) + { + if (comp != null) + { + trans = _host.CreateTransaction(string.Format(SR.DragDropMoveComponent, comp.Site.Name)); + } + } + + try + { + handler.EndDrag(components, cancel); + } + catch (Exception e) + { + Debug.Fail(e.ToString()); + } + } + finally + { + if (trans != null) + trans.Commit(); + // Reset the selection. This will re-display our selection. + Visible = _savedVisible; + ((ISelectionUIService)this).SyncSelection(); + if (_dragTransaction != null) + { + _dragTransaction.Commit(); + _dragTransaction = null; + } + // In case this drag was initiated by us, ensure that our mouse state is correct + EndMouseDrag(MousePosition); + } + } + + /// + /// Filters the set of selected components. The selection service will retrieve all components that are currently selected. This method allows you to filter this set down to components that match your criteria. The selectionRules parameter must contain one or more flags from the SelectionRules class. These flags allow you to constrain the set of selected objects to visible, movable, sizeable or all objects. + /// + object[] ISelectionUIService.FilterSelection(object[] components, SelectionRules selectionRules) + { + object[] selection = null; + if (components == null) + return new object[0]; + + // Mask off any selection object that doesn't adhere to the given ruleset. We can ignore this if the ruleset is zero, as all components would be accepted. + if (selectionRules != SelectionRules.None) + { + ArrayList list = new ArrayList(); + foreach (object comp in components) + { + SelectionUIItem item = (SelectionUIItem)_selectionItems[comp]; + if (item != null && !(item is ContainerSelectionUIItem)) + { + if ((item.GetRules() & selectionRules) == selectionRules) + { + list.Add(comp); + } + } + } + selection = (object[])list.ToArray(); + } + return selection ?? (new object[0]); + } + + /// + /// Retrieves the width and height of a selection border grab handle. Designers may need this to properly position their user interfaces. + /// + Size ISelectionUIService.GetAdornmentDimensions(AdornmentType adornmentType) + { + switch (adornmentType) + { + case AdornmentType.GrabHandle: + return new Size(SelectionUIItem.GRABHANDLE_WIDTH, SelectionUIItem.GRABHANDLE_HEIGHT); + case AdornmentType.ContainerSelector: + case AdornmentType.Maximum: + return new Size(ContainerSelectionUIItem.CONTAINER_WIDTH, ContainerSelectionUIItem.CONTAINER_HEIGHT); + } + return new Size(0, 0); + } + + /// + /// Tests to determine if the given screen coordinate is over an adornment for the specified component. This will only return true if the adornment, and selection UI, is visible. + /// + bool ISelectionUIService.GetAdornmentHitTest(object component, Point value) + { + if (GetHitTest(value, HITTEST_DEFAULT).hitTest != SelectionUIItem.NOHIT) + { + return true; + } + return false; + } + + /// + /// Determines if the component is currently "container" selected. Container selection is a visual aid for selecting containers. It doesn't affect the normal "component" selection. + /// + bool ISelectionUIService.GetContainerSelected(object component) => (component != null && _selectionItems[component] is ContainerSelectionUIItem); + + /// + /// Retrieves a set of flags that define rules for the selection. Selection rules indicate if the given component can be moved or sized, for example. + /// + SelectionRules ISelectionUIService.GetSelectionRules(object component) + { + SelectionUIItem sel = (SelectionUIItem)_selectionItems[component]; + if (sel == null) + { + Debug.Fail("The component is not currently selected."); + throw new InvalidOperationException(); + } + return sel.GetRules(); + } + + /// + /// Allows you to configure the style of the selection frame that a component uses. This is useful if your component supports different modes of operation (such as an in-place editing mode and a static design mode). Where possible, you should leave the selection style as is and use the design-time hit testing feature of the IDesigner interface to provide features at design time. The value of style must be one of the SelectionStyle enum values. The selection style is only valid for the duration that the component is selected. + /// + SelectionStyles ISelectionUIService.GetSelectionStyle(object component) + { + SelectionUIItem s = (SelectionUIItem)_selectionItems[component]; + if (s == null) + { + return SelectionStyles.None; + } + return s.Style; + } + + /// + /// Changes the container selection status of the given component. Container selection is a visual aid for selecting containers. It doesn't affect the normal "component" selection. + /// + void ISelectionUIService.SetContainerSelected(object component, bool selected) + { + if (selected) + { + SelectionUIItem existingItem = (SelectionUIItem)_selectionItems[component]; + if (!(existingItem is ContainerSelectionUIItem)) + { + if (existingItem != null) + { + existingItem.Dispose(); + } + SelectionUIItem item = new ContainerSelectionUIItem(this, component); + _selectionItems[component] = item; + + // Now update our region and invalidate + // + UpdateWindowRegion(); + if (existingItem != null) + { + existingItem.Invalidate(); + } + item.Invalidate(); + } + } + else + { + SelectionUIItem existingItem = (SelectionUIItem)_selectionItems[component]; + if (existingItem == null || existingItem is ContainerSelectionUIItem) + { + _selectionItems.Remove(component); + if (existingItem != null) + { + existingItem.Dispose(); + } + UpdateWindowRegion(); + existingItem.Invalidate(); + } + } + } + + /// + /// Allows you to configure the style of the selection frame that a component uses. This is useful if your component supports different modes of operation (such as an in-place editing mode and a static design mode). Where possible, you should leave the selection style as is and use the design-time hit testing feature of the IDesigner interface to provide features at design time. The value of style must be one of the SelectionStyle enum values. The selection style is only valid for the duration that the component is selected. + /// + void ISelectionUIService.SetSelectionStyle(object component, SelectionStyles style) + { + SelectionUIItem selUI = (SelectionUIItem)_selectionItems[component]; + if (_selSvc != null && _selSvc.GetComponentSelected(component)) + { + selUI = new SelectionUIItem(this, component); + _selectionItems[component] = selUI; + } + + if (selUI != null) + { + selUI.Style = style; + UpdateWindowRegion(); + selUI.Invalidate(); + } + } + + /// + /// This should be called when a component has been moved, sized or re-parented, but the change was not the result of a property change. All property changes are monitored by the selection UI service, so this is automatic most of the time. There are times, however, when a component may be moved without a property change notification occurring. Scrolling an auto scroll Win32 form is an example of this. This method simply re-queries all currently selected components for their bounds and udpates the selection handles for any that have changed. + /// + void ISelectionUIService.SyncSelection() + { + if (_batchMode) + { + _batchChanged = true; + } + else + { + if (IsHandleCreated) + { + bool updateRegion = false; + foreach (SelectionUIItem item in _selectionItems.Values) + { + updateRegion |= item.UpdateSize(); + item.UpdateRules(); + } + + if (updateRegion) + { + UpdateWindowRegion(); + Update(); + } + } + } + } + + /// + /// This should be called when a component's property changed, that the designer thinks should result in a selection UI change. This method simply re-queries all currently selected components for their bounds and udpates the selection handles for any that have changed. + /// + void ISelectionUIService.SyncComponent(object component) + { + if (_batchMode) + { + _batchSync = true; + } + else + { + if (IsHandleCreated) + { + foreach (SelectionUIItem item in _selectionItems.Values) + { + item.UpdateRules(); + item.Dispose(); + } + UpdateWindowRegion(); + Invalidate(); + Update(); + } + } + } + + /// + /// This class represents a single selected object. + /// + private class SelectionUIItem + { + // Flags describing how a given selection point may be sized + public const int SIZE_X = 0x0001; + public const int SIZE_Y = 0x0002; + public const int SIZE_MASK = 0x0003; + // Flags describing how a given selection point may be moved + public const int MOVE_X = 0x0004; + public const int MOVE_Y = 0x0008; + public const int MOVE_MASK = 0x000C; + // Flags describing where a given selection point is located on an object + public const int POS_LEFT = 0x0010; + public const int POS_TOP = 0x0020; + public const int POS_RIGHT = 0x0040; + public const int POS_BOTTOM = 0x0080; + public const int POS_MASK = 0x00F0; + // This is returned if the given selection point is not within the selection + public const int NOHIT = 0x0100; + // This is returned if the given selection point on the "container selector" + public const int CONTAINER_SELECTOR = 0x0200; + public const int GRABHANDLE_WIDTH = 7; + public const int GRABHANDLE_HEIGHT = 7; + // tables we use to determine how things can move and size + internal static readonly int[] s_activeSizeArray = new int[] { + SIZE_X | SIZE_Y | POS_LEFT | POS_TOP, SIZE_Y | POS_TOP, SIZE_X | SIZE_Y | POS_TOP | POS_RIGHT, + SIZE_X | POS_LEFT, SIZE_X | POS_RIGHT, + SIZE_X | SIZE_Y | POS_LEFT | POS_BOTTOM, SIZE_Y | POS_BOTTOM, SIZE_X | SIZE_Y | POS_RIGHT | POS_BOTTOM + }; + + internal static readonly Cursor[] s_activeCursorArrays = new Cursor[] { + Cursors.SizeNWSE, Cursors.SizeNS, Cursors.SizeNESW, + Cursors.SizeWE, Cursors.SizeWE, + Cursors.SizeNESW, Cursors.SizeNS, Cursors.SizeNWSE + }; + + internal static readonly int[] s_inactiveSizeArray = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + internal static readonly Cursor[] s_inactiveCursorArray = new Cursor[] { + Cursors.Arrow, Cursors.Arrow, Cursors.Arrow, + Cursors.Arrow, Cursors.Arrow, + Cursors.Arrow, Cursors.Arrow, Cursors.Arrow + }; + + internal int[] _sizes; // array of sizing rules for this selection + internal Cursor[] _cursors; // array of cursors for each grab location + internal SelectionUIService _selUIsvc; + internal Rectangle _innerRect = Rectangle.Empty; // inner part of selection (== control bounds) + internal Rectangle _outerRect = Rectangle.Empty; // outer part of selection (inner + border size) + internal Region _region; // region object that defines the shape + internal object _component; // the component we're rendering + private readonly Control _control; + private SelectionStyles _selectionStyle; // how do we draw this thing? + private SelectionRules _selectionRules; + private readonly ISelectionUIHandler _handler; // the components selection UI handler (can be null) + + /// Its ok to call virtual method as this is a private class. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public SelectionUIItem(SelectionUIService selUIsvc, object component) + { + _selUIsvc = selUIsvc; + _component = component; + _selectionStyle = SelectionStyles.Selected; + // By default, a component isn't visible. We must establish what it can do through it's UI handler. + _handler = selUIsvc.GetHandler(component); + _sizes = s_inactiveSizeArray; + _cursors = s_inactiveCursorArray; + if (component is IComponent comp) + { + if (selUIsvc._host.GetDesigner(comp) is ControlDesigner cd) + { + _control = cd.Control; + } + } + UpdateRules(); + UpdateGrabSettings(); + UpdateSize(); + } + + /// + /// Retrieves the style of the selection frame for this selection. + /// + public virtual SelectionStyles Style + { + get => _selectionStyle; + set + { + if (value != _selectionStyle) + { + _selectionStyle = value; + if (_region != null) + { + _region.Dispose(); + _region = null; + } + } + } + } + + /// + /// paints the selection + /// + public virtual void DoPaint(Graphics gr) + { + // If we're not visible, then there's nothing to do... + if ((GetRules() & SelectionRules.Visible) == SelectionRules.None) + return; + + bool fActive = false; + if (_selUIsvc._selSvc != null) + { + fActive = _component == _selUIsvc._selSvc.PrimarySelection; + // Office rules: If this is a multi-select, reverse the colors for active / inactive. + fActive = (fActive == (_selUIsvc._selSvc.SelectionCount <= 1)); + } + + Rectangle r = new Rectangle(_outerRect.X, _outerRect.Y, GRABHANDLE_WIDTH, GRABHANDLE_HEIGHT); + Rectangle inner = _innerRect; + Rectangle outer = _outerRect; + Region oldClip = gr.Clip; + Color borderColor = SystemColors.Control; + if (_control != null && _control.Parent != null) + { + Control parent = _control.Parent; + borderColor = parent.BackColor; + } + Brush brush = new SolidBrush(borderColor); + gr.ExcludeClip(inner); + gr.FillRectangle(brush, outer); + brush.Dispose(); + gr.Clip = oldClip; + ControlPaint.DrawSelectionFrame(gr, false, outer, inner, borderColor); + //if it's not locked & it is sizeable... + if (((GetRules() & SelectionRules.Locked) == SelectionRules.None) && (GetRules() & SelectionRules.AllSizeable) != SelectionRules.None) + { + // upper left + ControlPaint.DrawGrabHandle(gr, r, fActive, (_sizes[0] != 0)); + // upper right + r.X = inner.X + inner.Width; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[2] != 0); + // lower right + r.Y = inner.Y + inner.Height; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[7] != 0); + // lower left + r.X = outer.X; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[5] != 0); + // lower middle + r.X += (outer.Width - GRABHANDLE_WIDTH) / 2; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[6] != 0); + // upper middle + r.Y = outer.Y; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[1] != 0); + // left middle + r.X = outer.X; + r.Y = inner.Y + (inner.Height - GRABHANDLE_HEIGHT) / 2; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[3] != 0); + // right middle + r.X = inner.X + inner.Width; + ControlPaint.DrawGrabHandle(gr, r, fActive, _sizes[4] != 0); + } + else + { + ControlPaint.DrawLockedFrame(gr, outer, fActive); + } + } + + /// + /// Retrieves an appropriate cursor at the given point. If there is no appropriate cursor here (ie, the point lies outside the selection rectangle), then this will return null. + /// + public virtual Cursor GetCursorAtPoint(Point pt) + { + Cursor cursor = null; + if (PointWithinSelection(pt)) + { + int nOffset = -1; + if ((GetRules() & SelectionRules.AllSizeable) != SelectionRules.None) + { + nOffset = GetHandleIndexOfPoint(pt); + } + + if (-1 == nOffset) + { + if ((GetRules() & SelectionRules.Moveable) == SelectionRules.None) + { + cursor = Cursors.Default; + } + else + { + cursor = Cursors.SizeAll; + } + } + else + { + cursor = _cursors[nOffset]; + } + } + return cursor; + } + + /// + /// returns the hit test code of the given point. This may be one of: + /// + public virtual int GetHitTest(Point pt) + { + // Is it within our rects? + if (!PointWithinSelection(pt)) + { + return NOHIT; + } + + // Which index in the array is this? + int nOffset = GetHandleIndexOfPoint(pt); + + // If no index, the user has picked on the hatch + if (-1 == nOffset || _sizes[nOffset] == 0) + { + return ((GetRules() & SelectionRules.Moveable) == SelectionRules.None ? 0 : MOVE_X | MOVE_Y); + } + return _sizes[nOffset]; + } + + + /// + /// gets the array offset of the handle at the given point + /// + private int GetHandleIndexOfPoint(Point pt) + { + if (pt.X >= _outerRect.X && pt.X <= _innerRect.X) + { + // Something on the left side. + if (pt.Y >= _outerRect.Y && pt.Y <= _innerRect.Y) + return 0; // top left + if (pt.Y >= _innerRect.Y + _innerRect.Height && pt.Y <= _outerRect.Y + _outerRect.Height) + return 5; // bottom left + if (pt.Y >= _outerRect.Y + (_outerRect.Height - GRABHANDLE_HEIGHT) / 2 + && pt.Y <= _outerRect.Y + (_outerRect.Height + GRABHANDLE_HEIGHT) / 2) + return 3; // middle left + return -1; // unknown hit + } + + if (pt.Y >= _outerRect.Y && pt.Y <= _innerRect.Y) + { + // something on the top + Debug.Assert(!(pt.X >= _outerRect.X && pt.X <= _innerRect.X), "Should be handled by left top check"); + if (pt.X >= _innerRect.X + _innerRect.Width && pt.X <= _outerRect.X + _outerRect.Width) + return 2; // top right + if (pt.X >= _outerRect.X + (_outerRect.Width - GRABHANDLE_WIDTH) / 2 + && pt.X <= _outerRect.X + (_outerRect.Width + GRABHANDLE_WIDTH) / 2) + return 1; // top middle + return -1; // unknown hit + } + + if (pt.X >= _innerRect.X + _innerRect.Width && pt.X <= _outerRect.X + _outerRect.Width) + { + // something on the right side + Debug.Assert(!(pt.Y >= _outerRect.Y && pt.Y <= _innerRect.Y), "Should be handled by top right check"); + if (pt.Y >= _innerRect.Y + _innerRect.Height && pt.Y <= _outerRect.Y + _outerRect.Height) + return 7; // bottom right + if (pt.Y >= _outerRect.Y + (_outerRect.Height - GRABHANDLE_HEIGHT) / 2 + && pt.Y <= _outerRect.Y + (_outerRect.Height + GRABHANDLE_HEIGHT) / 2) + return 4; // middle right + return -1; // unknown hit + } + + if (pt.Y >= _innerRect.Y + _innerRect.Height && pt.Y <= _outerRect.Y + _outerRect.Height) + { + // something on the bottom + Debug.Assert(!(pt.X >= _outerRect.X && pt.X <= _innerRect.X), "Should be handled by left bottom check"); + Debug.Assert(!(pt.X >= _innerRect.X + _innerRect.Width && pt.X <= _outerRect.X + _outerRect.Width), "Should be handled by right bottom check"); + if (pt.X >= _outerRect.X + (_outerRect.Width - GRABHANDLE_WIDTH) / 2 && pt.X <= _outerRect.X + (_outerRect.Width + GRABHANDLE_WIDTH) / 2) + return 6; // bottom middle + return -1; // unknown hit + } + return -1; // unknown hit + } + + /// + /// returns a region handle that defines this selection. This is used to piece together a paint region for the surface that we draw our selection handles on + /// + public virtual Region GetRegion() + { + if (_region == null) + { + if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !_outerRect.IsEmpty) + { + _region = new Region(_outerRect); + _region.Exclude(_innerRect); + } + else + { + _region = new Region(new Rectangle(0, 0, 0, 0)); + } + + if (_handler != null) + { + Rectangle handlerClip = _handler.GetSelectionClipRect(_component); + if (!handlerClip.IsEmpty) + { + _region.Intersect(_selUIsvc.RectangleToClient(handlerClip)); + } + } + } + return _region; + } + + /// + /// Retrieves the rules associated with this selection. + /// + public SelectionRules GetRules() => _selectionRules; + + public void Dispose() + { + if (_region != null) + { + _region.Dispose(); + _region = null; + } + } + + /// + /// Invalidates the region for this selection glyph. + /// + public void Invalidate() + { + if (!_outerRect.IsEmpty && !_selUIsvc.Disposing) + { + _selUIsvc.Invalidate(_outerRect); + } + } + + /// + /// Part of our hit testing logic; determines if the point is somewhere within our selection. + /// + protected bool PointWithinSelection(Point pt) + { + // This is only supported for visible selections + if ((GetRules() & SelectionRules.Visible) == SelectionRules.None || _outerRect.IsEmpty || _innerRect.IsEmpty) + { + return false; + } + + if (pt.X < _outerRect.X || pt.X > _outerRect.X + _outerRect.Width) + { + return false; + } + + if (pt.Y < _outerRect.Y || pt.Y > _outerRect.Y + _outerRect.Height) + { + return false; + } + + if (pt.X > _innerRect.X + && pt.X < _innerRect.X + _innerRect.Width + && pt.Y > _innerRect.Y + && pt.Y < _innerRect.Y + _innerRect.Height) + { + return false; + } + return true; + } + + /// + /// Updates the available grab handle settings based on the current rules. + /// + private void UpdateGrabSettings() + { + SelectionRules rules = GetRules(); + if ((rules & SelectionRules.AllSizeable) == SelectionRules.None) + { + _sizes = s_inactiveSizeArray; + _cursors = s_inactiveCursorArray; + } + else + { + _sizes = new int[8]; + _cursors = new Cursor[8]; + Array.Copy(s_activeCursorArrays, _cursors, _cursors.Length); + Array.Copy(s_activeSizeArray, _sizes, _sizes.Length); + if ((rules & SelectionRules.TopSizeable) != SelectionRules.TopSizeable) + { + _sizes[0] = 0; + _sizes[1] = 0; + _sizes[2] = 0; + _cursors[0] = Cursors.Arrow; + _cursors[1] = Cursors.Arrow; + _cursors[2] = Cursors.Arrow; + } + if ((rules & SelectionRules.LeftSizeable) != SelectionRules.LeftSizeable) + { + _sizes[0] = 0; + _sizes[3] = 0; + _sizes[5] = 0; + _cursors[0] = Cursors.Arrow; + _cursors[3] = Cursors.Arrow; + _cursors[5] = Cursors.Arrow; + } + if ((rules & SelectionRules.BottomSizeable) != SelectionRules.BottomSizeable) + { + _sizes[5] = 0; + _sizes[6] = 0; + _sizes[7] = 0; + _cursors[5] = Cursors.Arrow; + _cursors[6] = Cursors.Arrow; + _cursors[7] = Cursors.Arrow; + } + if ((rules & SelectionRules.RightSizeable) != SelectionRules.RightSizeable) + { + _sizes[2] = 0; + _sizes[4] = 0; + _sizes[7] = 0; + _cursors[2] = Cursors.Arrow; + _cursors[4] = Cursors.Arrow; + _cursors[7] = Cursors.Arrow; + } + } + } + + /// + /// Updates our cached selection rules based on current handler values. + /// + public void UpdateRules() + { + if (_handler == null) + { + _selectionRules = SelectionRules.None; + } + else + { + SelectionRules oldRules = _selectionRules; + _selectionRules = _handler.GetComponentRules(_component); + if (_selectionRules != oldRules) + { + UpdateGrabSettings(); + Invalidate(); + } + } + } + + /// + /// rebuilds the inner and outer rectangles based on the current selItem.component dimensions. We could calcuate this every time, but that would be expensive for functions like getHitTest that are called a lot (like on every mouse move) + /// + public virtual bool UpdateSize() + { + bool sizeChanged = false; + // Short circuit common cases + if (_handler == null) + return false; + if ((GetRules() & SelectionRules.Visible) == SelectionRules.None) + return false; + + _innerRect = _handler.GetComponentBounds(_component); + if (!_innerRect.IsEmpty) + { + _innerRect = _selUIsvc.RectangleToClient(_innerRect); + Rectangle rcOuterNew = new Rectangle( + _innerRect.X - GRABHANDLE_WIDTH, + _innerRect.Y - GRABHANDLE_HEIGHT, + _innerRect.Width + 2 * GRABHANDLE_WIDTH, + _innerRect.Height + 2 * GRABHANDLE_HEIGHT); + if (_outerRect.IsEmpty || !_outerRect.Equals(rcOuterNew)) + { + if (!_outerRect.IsEmpty) + Invalidate(); + _outerRect = rcOuterNew; + Invalidate(); + if (_region != null) + { + _region.Dispose(); + _region = null; + } + sizeChanged = true; + } + } + else + { + Rectangle rcNew = new Rectangle(0, 0, 0, 0); + sizeChanged = _outerRect.IsEmpty || !_outerRect.Equals(rcNew); + _innerRect = _outerRect = rcNew; + } + return sizeChanged; + } + } + + private class ContainerSelectionUIItem : SelectionUIItem + { + public const int CONTAINER_WIDTH = 13; + public const int CONTAINER_HEIGHT = 13; + + public ContainerSelectionUIItem(SelectionUIService selUIsvc, object component) : base(selUIsvc, component) + { + } + + public override Cursor GetCursorAtPoint(Point pt) + { + if ((GetHitTest(pt) & CONTAINER_SELECTOR) != 0 && (GetRules() & SelectionRules.Moveable) != SelectionRules.None) + { + return Cursors.SizeAll; + } + else + { + return null; + } + } + + public override int GetHitTest(Point pt) + { + int ht = NOHIT; + if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !_outerRect.IsEmpty) + { + Rectangle r = new Rectangle(_outerRect.X, _outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); + if (r.Contains(pt)) + { + ht = CONTAINER_SELECTOR; + if ((GetRules() & SelectionRules.Moveable) != SelectionRules.None) + { + ht |= MOVE_X | MOVE_Y; + } + } + } + return ht; + } + + public override void DoPaint(Graphics gr) + { + // If we're not visible, then there's nothing to do... + if ((GetRules() & SelectionRules.Visible) == SelectionRules.None) + return; + Rectangle glyphBounds = new Rectangle(_outerRect.X, _outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); + ControlPaint.DrawContainerGrabHandle(gr, glyphBounds); + } + + public override Region GetRegion() + { + if (_region == null) + { + if ((GetRules() & SelectionRules.Visible) != SelectionRules.None && !_outerRect.IsEmpty) + { + Rectangle r = new Rectangle(_outerRect.X, _outerRect.Y, CONTAINER_WIDTH, CONTAINER_HEIGHT); + _region = new Region(r); + } + else + { + _region = new Region(new Rectangle(0, 0, 0, 0)); + } + } + return _region; + } + } + + private struct HitTestInfo + { + public readonly int hitTest; + public readonly SelectionUIItem selectionUIHit; + public readonly bool containerSelector; + + public HitTestInfo(int hitTest, SelectionUIItem selectionUIHit) + { + this.hitTest = hitTest; + this.selectionUIHit = selectionUIHit; + containerSelector = false; + } + + public HitTestInfo(int hitTest, SelectionUIItem selectionUIHit, bool containerSelector) + { + this.hitTest = hitTest; + this.selectionUIHit = selectionUIHit; + this.containerSelector = containerSelector; + } + + // Standard 'catch all - rethrow critical' exception pattern + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] + public override bool Equals(object obj) + { + try + { + HitTestInfo hi = (HitTestInfo)obj; + return hitTest == hi.hitTest && selectionUIHit == hi.selectionUIHit && containerSelector == hi.containerSelector; + } + catch (Exception ex) + { + if (ClientUtils.IsCriticalException(ex)) + { + throw; + } + } + return false; + } + + public static bool operator ==(HitTestInfo left, HitTestInfo right) + { + return (left.hitTest == right.hitTest + && left.selectionUIHit == right.selectionUIHit + && left.containerSelector == right.containerSelector); + } + + public static bool operator !=(HitTestInfo left, HitTestInfo right) => !(left == right); + + public override int GetHashCode() + { + int hash = hitTest | selectionUIHit.GetHashCode(); + if (containerSelector) + { + hash |= 0x10000; + } + return hash; + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/StatusCommandUI.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/StatusCommandUI.cs new file mode 100644 index 00000000000..f2811d54a89 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/StatusCommandUI.cs @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; + +namespace System.Windows.Forms.Design +{ + /// + /// This class provides a single entrypoint used by the Behaviors, KeySize and KeyMoves (in CommandSets) and SelectionService to update the StatusBar Information. + /// + internal class StatusCommandUI + { + private MenuCommand _statusRectCommand = null; + private IMenuCommandService _menuService = null; + private readonly IServiceProvider _serviceProvider; + + public StatusCommandUI(IServiceProvider provider) + { + _serviceProvider = provider; + } + + /// + /// Retrieves the menu editor service, which we cache for speed. + /// + private IMenuCommandService MenuService + { + get + { + if (_menuService == null) + { + _menuService = (IMenuCommandService)_serviceProvider.GetService(typeof(IMenuCommandService)); + } + return _menuService; + } + } + + /// + /// Retrieves the actual StatusRectCommand, which we cache for speed. + /// + private MenuCommand StatusRectCommand + { + get + { + if (_statusRectCommand == null) + { + if (MenuService != null) + { + _statusRectCommand = MenuService.FindCommand(MenuCommands.SetStatusRectangle); + } + } + return _statusRectCommand; + } + } + + /// + /// Actual Function which invokes the command. + /// + public void SetStatusInformation(Component selectedComponent, Point location) + { + if (selectedComponent == null) + { + return; + } + Rectangle bounds = Rectangle.Empty; + if (selectedComponent is Control c) + { + bounds = c.Bounds; + } + else + { + PropertyDescriptor BoundsProp = TypeDescriptor.GetProperties(selectedComponent)["Bounds"]; + if (BoundsProp != null && typeof(Rectangle).IsAssignableFrom(BoundsProp.PropertyType)) + { + bounds = (Rectangle)BoundsProp.GetValue(selectedComponent); + } + } + if (location != Point.Empty) + { + bounds.X = location.X; + bounds.Y = location.Y; + } + if (StatusRectCommand != null) + { + StatusRectCommand.Invoke(bounds); + } + } + + /// + /// Actual Function which invokes the command. + /// + public void SetStatusInformation(Component selectedComponent) + { + if (selectedComponent == null) + { + return; + } + Rectangle bounds = Rectangle.Empty; + if (selectedComponent is Control c) + { + bounds = c.Bounds; + } + else + { + PropertyDescriptor BoundsProp = TypeDescriptor.GetProperties(selectedComponent)["Bounds"]; + if (BoundsProp != null && typeof(Rectangle).IsAssignableFrom(BoundsProp.PropertyType)) + { + bounds = (Rectangle)BoundsProp.GetValue(selectedComponent); + } + } + if (StatusRectCommand != null) + { + StatusRectCommand.Invoke(bounds); + } + } + + /// + /// Actual Function which invokes the command. + /// + public void SetStatusInformation(Rectangle bounds) + { + if (StatusRectCommand != null) + { + StatusRectCommand.Invoke(bounds); + } + } + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ThemedScrollbarMode.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ThemedScrollbarMode.cs new file mode 100644 index 00000000000..7b0428638f1 --- /dev/null +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/ThemedScrollbarMode.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; + +namespace System.Windows.Forms.Design +{ + public enum ThemedScrollbarMode + { + /// + /// The window and all of its children will have themed scrollbars + /// + All = 1, + + /// + /// The window and all of its children will be un-themed + /// + None = 2, + + /// + /// The window will have themed scrollbars but all of its children will be un-themed + /// + OnlyTopLevel = 3 + }; + + public struct ThemedScrollbarWindow + { + public IntPtr Handle; + public ThemedScrollbarMode Mode; + }; + + /// + /// Returns an enumeration of windows and flags of how their scrollbars need to be themed + /// when the designer is running inside Visual Studio. + /// + public interface IContainsThemedScrollbarWindows + { + IEnumerable ThemedScrollbarWindows(); + } +} diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/WindowsFormsDesignerOptionService.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/WindowsFormsDesignerOptionService.cs index 2bd233dad39..548a2b526c3 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/WindowsFormsDesignerOptionService.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/WindowsFormsDesignerOptionService.cs @@ -11,12 +11,34 @@ namespace System.Windows.Forms.Design /// public class WindowsFormsDesignerOptionService : DesignerOptionService { - public virtual DesignerOptions CompatibilityOptions => throw new NotImplementedException(SR.NotImplementedByDesign); + private DesignerOptions _options; + + public virtual DesignerOptions CompatibilityOptions + { + get + { + if (_options == null) + { + _options = new DesignerOptions(); + } + return _options; + } + } /// /// This method is called on demand the first time a user asks for child /// options or properties of an options collection. /// - protected override void PopulateOptionCollection(DesignerOptionCollection options) => throw new NotImplementedException(SR.NotImplementedByDesign); + protected override void PopulateOptionCollection(DesignerOptionCollection options) + { + if (options.Parent == null) + { + DesignerOptions designerOptions = CompatibilityOptions; + if (designerOptions != null) + { + CreateOptionCollection(options, "DesignerOptions", designerOptions); + } + } + } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewMethods.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewMethods.cs index a3aea550e95..dd1ecada07a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewMethods.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewMethods.cs @@ -11397,9 +11397,9 @@ protected virtual void OnAutoSizeColumnsModeChanged(DataGridViewAutoSizeColumnsM DataGridViewAutoSizeColumnMode[] previousModes = e.PreviousModes; if (previousModes == null) { - throw new ArgumentNullException("e.PreviousModes"); + throw new ArgumentNullException(nameof(e.PreviousModes)); } - if (previousModes.Length != this.Columns.Count) + if (previousModes.Length != Columns.Count) { throw new ArgumentException(string.Format(SR.DataGridView_PreviousModesHasWrongLength)); }