From b3c72c2d587f515d0d2bd0d7ddab220626aba24b Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Fri, 1 Mar 2024 10:15:49 -0800 Subject: [PATCH] Improve ThemedMessageBox with wrapping logic and default system icons. Move Eto.IO.SystemIcons to Eto.Drawing, and add Information, Warning, Error, and Question icons. --- src/Eto.Gtk/Drawing/SystemIconsHandler.cs | 19 ++ src/Eto.Gtk/IO/SystemIcons.cs | 22 -- src/Eto.Gtk/NativeMethods.cs | 6 +- src/Eto.Gtk/Platform.cs | 2 - src/Eto.Mac/Drawing/SystemIconsHandler.cs | 54 ++++ src/Eto.Mac/IO/SystemIconsHandler.cs | 35 --- src/Eto.Mac/MacConversions.cs | 8 + src/Eto.Mac/Platform.cs | 2 - src/Eto.WinForms/Drawing/IconHandler.cs | 113 +++++---- src/Eto.WinForms/Eto.WinForms.csproj | 6 + .../Forms/Controls/TreeGridViewHandler.cs | 2 +- src/Eto.WinForms/IO/ShellIcon.cs | 240 ------------------ src/Eto.WinForms/IO/SystemIcons.cs | 58 ----- src/Eto.WinForms/Platform.cs | 2 - src/Eto.WinForms/Win32.cs | 139 +++++++++- src/Eto.Wpf/Drawing/FormattedTextHandler.cs | 2 +- src/Eto.Wpf/Drawing/SystemIconsHandler.cs | 69 +++++ src/Eto.Wpf/Eto.Wpf.csproj | 3 - src/Eto.Wpf/Forms/WpfFrameworkElement.cs | 2 +- src/Eto.Wpf/Forms/WpfWindow.cs | 33 ++- src/Eto.Wpf/IO/SystemIcons.cs | 58 ----- src/Eto.Wpf/Platform.cs | 13 +- src/Eto.Wpf/ShellIcon.cs | 220 ++++++++++++++++ src/Eto.Wpf/WpfConversions.cs | 8 +- src/Eto/Drawing/SystemIcons.cs | 144 +++++++++++ .../ThemedControls/ThemedMessageBoxHandler.cs | 121 +++++++-- src/Eto/IO/SystemIcons.cs | 66 +---- .../Sections/Drawing/SystemIconsSection.cs | 114 +++++++++ .../UnitTests/Forms/MessageBoxTests.cs | 85 ++++--- .../UnitTests/Forms/ThemedMessageBoxTests.cs | 33 +++ test/Eto.Test/Utility.cs | 10 +- 31 files changed, 1093 insertions(+), 596 deletions(-) create mode 100644 src/Eto.Gtk/Drawing/SystemIconsHandler.cs delete mode 100644 src/Eto.Gtk/IO/SystemIcons.cs create mode 100644 src/Eto.Mac/Drawing/SystemIconsHandler.cs delete mode 100644 src/Eto.Mac/IO/SystemIconsHandler.cs delete mode 100644 src/Eto.WinForms/IO/ShellIcon.cs delete mode 100644 src/Eto.WinForms/IO/SystemIcons.cs create mode 100644 src/Eto.Wpf/Drawing/SystemIconsHandler.cs delete mode 100644 src/Eto.Wpf/IO/SystemIcons.cs create mode 100644 src/Eto.Wpf/ShellIcon.cs create mode 100644 src/Eto/Drawing/SystemIcons.cs create mode 100644 test/Eto.Test/Sections/Drawing/SystemIconsSection.cs create mode 100755 test/Eto.Test/UnitTests/Forms/ThemedMessageBoxTests.cs diff --git a/src/Eto.Gtk/Drawing/SystemIconsHandler.cs b/src/Eto.Gtk/Drawing/SystemIconsHandler.cs new file mode 100644 index 0000000000..d5f98140fb --- /dev/null +++ b/src/Eto.Gtk/Drawing/SystemIconsHandler.cs @@ -0,0 +1,19 @@ +namespace Eto.GtkSharp.Drawing; + +public class SystemIconsHandler : SystemIcons.IHandler +{ + #region ISystemIcons Members + + public Icon GetFileIcon(string fileName, SystemIconSize size) + { + return null; + } + + public Icon Get(SystemIconType type, SystemIconSize size) + { + return null; + } + + #endregion + +} diff --git a/src/Eto.Gtk/IO/SystemIcons.cs b/src/Eto.Gtk/IO/SystemIcons.cs deleted file mode 100644 index 59d541de1c..0000000000 --- a/src/Eto.Gtk/IO/SystemIcons.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Eto.IO; - -namespace Eto.GtkSharp.IO -{ - public class SystemIconsHandler : SystemIcons.IHandler - { - #region ISystemIcons Members - - public Icon GetFileIcon(string fileName, IconSize size) - { - return null; - } - - public Icon GetStaticIcon(StaticIconType type, IconSize size) - { - return null; - } - - #endregion - - } -} diff --git a/src/Eto.Gtk/NativeMethods.cs b/src/Eto.Gtk/NativeMethods.cs index f99eb67d78..0c5d4a52e3 100644 --- a/src/Eto.Gtk/NativeMethods.cs +++ b/src/Eto.Gtk/NativeMethods.cs @@ -30,7 +30,7 @@ public struct FcFontSet static class NMWindows { -#if NETCOREAPP +#if NET static NMWindows() { @@ -255,7 +255,7 @@ static NMWindows() static class NMLinux { -#if NETCOREAPP +#if NET static NMLinux() { @@ -480,7 +480,7 @@ static NMLinux() static class NMMac { -#if NETCOREAPP +#if NET static NMMac() { diff --git a/src/Eto.Gtk/Platform.cs b/src/Eto.Gtk/Platform.cs index 26a9a2b673..744500518a 100644 --- a/src/Eto.Gtk/Platform.cs +++ b/src/Eto.Gtk/Platform.cs @@ -1,10 +1,8 @@ -using Eto.IO; using Eto.GtkSharp.Drawing; using Eto.GtkSharp.Forms.Cells; using Eto.GtkSharp.Forms.Controls; using Eto.GtkSharp.Forms.Printing; using Eto.GtkSharp.Forms; -using Eto.GtkSharp.IO; using Eto.Forms.ThemedControls; using Eto.GtkSharp.Forms.Menu; using Eto.GtkSharp.Forms.ToolBar; diff --git a/src/Eto.Mac/Drawing/SystemIconsHandler.cs b/src/Eto.Mac/Drawing/SystemIconsHandler.cs new file mode 100644 index 0000000000..ae19a7639c --- /dev/null +++ b/src/Eto.Mac/Drawing/SystemIconsHandler.cs @@ -0,0 +1,54 @@ +using Eto.Mac.Drawing; + +namespace Eto.Mac.Drawing; + +public class SystemIconsHandler : SystemIcons.IHandler +{ + + public Icon GetFileIcon(string fileName, SystemIconSize size) + { + var ws = new NSWorkspace(); + var image = ws.IconForFileType(Path.GetExtension(fileName)); + return WithSize(new Icon(new IconHandler(image)), size); + } + + public Icon Get(SystemIconType type, SystemIconSize size) + { + var icon = type switch + { + SystemIconType.OpenDirectory => new Icon(new IconHandler(NSImage.ImageNamed(NSImageName.Folder))), + SystemIconType.CloseDirectory => new Icon(new IconHandler(NSImage.ImageNamed(NSImageName.Folder))), + SystemIconType.Question => GetResourceIcon("GenericQuestionMarkIcon.icns"), + SystemIconType.Error => GetResourceIcon("AlertStopIcon.icns"), + SystemIconType.Information => GetResourceIcon("AlertNoteIcon.icns"), + SystemIconType.Warning => new Icon(new IconHandler(NSImage.ImageNamed(NSImageName.Caution))), + _ => throw new NotSupportedException(), + }; + + return WithSize(icon, size); + } + + private static Icon WithSize(Icon icon, SystemIconSize size) + { + var pixels = size switch + { + SystemIconSize.Large => 32, + SystemIconSize.Small => 16, + _ => throw new NotSupportedException() + }; + + return icon.WithSize(pixels, pixels); + } + + private static Icon GetResourceIcon(string name) + { + const string basePath = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources"; + var path = Path.Combine(basePath, name); + if (File.Exists(path)) + { + return new Icon(path); + } + return null; + } +} + diff --git a/src/Eto.Mac/IO/SystemIconsHandler.cs b/src/Eto.Mac/IO/SystemIconsHandler.cs deleted file mode 100644 index 9b17b85925..0000000000 --- a/src/Eto.Mac/IO/SystemIconsHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Eto.IO; -using Eto.Mac.Drawing; -namespace Eto.Mac.IO -{ - public class SystemIconsHandler : SystemIcons.IHandler - { - - public Icon GetFileIcon(string fileName, IconSize size) - { - var ws = new NSWorkspace(); - var image = ws.IconForFileType(Path.GetExtension(fileName)); - return new Icon(new IconHandler(image)); - } - - public Icon GetStaticIcon(StaticIconType type, IconSize size) - { - var ws = new NSWorkspace(); - string code; - switch (type) - { - case StaticIconType.OpenDirectory: - code = "ofld"; - break; - case StaticIconType.CloseDirectory: - code = "ofld"; - break; - default: - throw new NotSupportedException(); - } - var image = ws.IconForFileType(code); - return new Icon(new IconHandler(image)); - } - } -} - diff --git a/src/Eto.Mac/MacConversions.cs b/src/Eto.Mac/MacConversions.cs index 26d437f778..5ccfe01042 100644 --- a/src/Eto.Mac/MacConversions.cs +++ b/src/Eto.Mac/MacConversions.cs @@ -658,5 +658,13 @@ public static NSTextAlignment ToNS(this FormattedTextAlignment align) throw new NotSupportedException(); } } + + public static Image ToEto(this NSImage image) + { + if (image.Representations().Length == 1) + return new Bitmap(new BitmapHandler(image)); + else + return new Icon(new IconHandler(image)); + } } } diff --git a/src/Eto.Mac/Platform.cs b/src/Eto.Mac/Platform.cs index ab39b2744b..2df670a948 100644 --- a/src/Eto.Mac/Platform.cs +++ b/src/Eto.Mac/Platform.cs @@ -1,6 +1,4 @@ -using Eto.IO; using Eto.Mac.Drawing; -using Eto.Mac.IO; using Eto.Mac.Forms.Controls; using Eto.Mac.Forms.Printing; using Eto.Mac.Forms; diff --git a/src/Eto.WinForms/Drawing/IconHandler.cs b/src/Eto.WinForms/Drawing/IconHandler.cs index 18e246640a..f73c1cbe58 100644 --- a/src/Eto.WinForms/Drawing/IconHandler.cs +++ b/src/Eto.WinForms/Drawing/IconHandler.cs @@ -1,3 +1,5 @@ +using System.Windows.Controls; + namespace Eto.WinForms.Drawing { public interface IWindowsIconSource @@ -7,14 +9,20 @@ public interface IWindowsIconSource public class IconHandler : WidgetHandler, Icon.IHandler, IWindowsImage, IWindowsIconSource { - Dictionary cachedImages; - List frames; - IconFrame idealFrame; + Dictionary _cachedImages; + List _frames; + IconFrame _idealFrame; + bool _destroyIcon; public IconHandler(sd.Icon control) { this.Control = control; } + public IconHandler(sd.Icon control, bool destroyIcon) + { + this.Control = control; + _destroyIcon = destroyIcon; + } public IconHandler() { @@ -33,7 +41,7 @@ public IEnumerable Frames { get { - return frames ?? SplitIcon(Control); + return _frames ?? SplitIcon(Control); } } @@ -49,11 +57,11 @@ public void Create(string fileName) public IconFrame GetIdealIcon() { - if (idealFrame != null) - return idealFrame; + if (_idealFrame != null) + return _idealFrame; var orderedFrames = SplitIcon(Control).OrderByDescending(r => r.PixelSize.Width * r.PixelSize.Height); - idealFrame = orderedFrames.FirstOrDefault(r => r.Scale == 1) ?? orderedFrames.First(); - return idealFrame; + _idealFrame = orderedFrames.FirstOrDefault(r => r.Scale == 1) ?? orderedFrames.First(); + return _idealFrame; } public sd.Icon GetIconClosestToSize(int width) @@ -75,8 +83,8 @@ public sd.Icon GetIconClosestToSize(int width) public List SplitIcon(sd.Icon icon) { - if (frames != null) - return frames; + if (_frames != null) + return _frames; if (icon == null) { throw new ArgumentNullException("icon"); @@ -94,54 +102,65 @@ public List SplitIcon(sd.Icon icon) var splitIcons = new List(); int count = BitConverter.ToInt16(srcBuf, 4); // ICONDIR.idCount - for (int i = 0; i < count; i++) + if (count == 1) + { + splitIcons.Add(icon); + } + else { - using (var destStream = new MemoryStream()) - using (var writer = new BinaryWriter(destStream)) + for (int i = 0; i < count; i++) { - // Copy ICONDIR and ICONDIRENTRY. - int pos = 0; - writer.Write(srcBuf, pos, sICONDIR - 2); - writer.Write((short)1); // ICONDIR.idCount == 1; - - pos += sICONDIR; - pos += sICONDIRENTRY * i; - - writer.Write(srcBuf, pos, sICONDIRENTRY - 4); // write out icon info (minus old offset) - writer.Write(sICONDIR + sICONDIRENTRY); // write offset of icon data - pos += 8; - - // Copy picture and mask data. - int imgSize = BitConverter.ToInt32(srcBuf, pos); // ICONDIRENTRY.dwBytesInRes - pos += 4; - int imgOffset = BitConverter.ToInt32(srcBuf, pos); // ICONDIRENTRY.dwImageOffset - if (imgOffset + imgSize > srcBuf.Length) - throw new ArgumentException("ugh"); - writer.Write(srcBuf, imgOffset, imgSize); - writer.Flush(); - - // Create new icon. - destStream.Seek(0, SeekOrigin.Begin); - splitIcons.Add(new sd.Icon(destStream)); + using (var destStream = new MemoryStream()) + using (var writer = new BinaryWriter(destStream)) + { + // Copy ICONDIR and ICONDIRENTRY. + int pos = 0; + writer.Write(srcBuf, pos, sICONDIR - 2); + writer.Write((short)1); // ICONDIR.idCount == 1; + + pos += sICONDIR; + pos += sICONDIRENTRY * i; + + writer.Write(srcBuf, pos, sICONDIRENTRY - 4); // write out icon info (minus old offset) + writer.Write(sICONDIR + sICONDIRENTRY); // write offset of icon data + pos += 8; + + // Copy picture and mask data. + int imgSize = BitConverter.ToInt32(srcBuf, pos); // ICONDIRENTRY.dwBytesInRes + pos += 4; + int imgOffset = BitConverter.ToInt32(srcBuf, pos); // ICONDIRENTRY.dwImageOffset + if (imgOffset + imgSize > srcBuf.Length) + throw new ArgumentException("ugh"); + writer.Write(srcBuf, imgOffset, imgSize); + writer.Flush(); + + // Create new icon. + destStream.Seek(0, SeekOrigin.Begin); + splitIcons.Add(new sd.Icon(destStream)); + } } } - frames = splitIcons.Select(r => IconFrame.FromControlObject(1, r)).ToList(); - return frames; + _frames = splitIcons.Select(r => IconFrame.FromControlObject(1, r)).ToList(); + return _frames; } protected override void Dispose(bool disposing) { + if (_destroyIcon && Control != null) + { + Win32.DestroyIcon(Control.Handle); + } base.Dispose(disposing); if (disposing) { - if (frames != null) + if (_frames != null) { - foreach (var frame in frames) + foreach (var frame in _frames) { ((sd.Icon)frame.ControlObject).Dispose(); } - frames = null; + _frames = null; } } } @@ -150,15 +169,15 @@ public sd.Image GetImageWithSize(int? size) { if (size != null) { - if (cachedImages == null) - cachedImages = new Dictionary(); + if (_cachedImages == null) + _cachedImages = new Dictionary(); - if (cachedImages.TryGetValue(size.Value, out var bmp)) + if (_cachedImages.TryGetValue(size.Value, out var bmp)) return bmp; var icon = GetIconClosestToSize(size.Value); bmp = icon.ToBitmap(); - cachedImages[size.Value] = bmp; + _cachedImages[size.Value] = bmp; return bmp; } return GetIdealIcon().Bitmap.ToSD(); @@ -203,7 +222,7 @@ public sd.Icon GetIcon() public void Create(IEnumerable frames) { - this.frames = frames.ToList(); + this._frames = frames.ToList(); var frame = GetIdealIcon(); size = frame.Size; Control = (sd.Icon)frame.ControlObject; diff --git a/src/Eto.WinForms/Eto.WinForms.csproj b/src/Eto.WinForms/Eto.WinForms.csproj index e144fdad3c..172ba5ba58 100755 --- a/src/Eto.WinForms/Eto.WinForms.csproj +++ b/src/Eto.WinForms/Eto.WinForms.csproj @@ -111,6 +111,12 @@ You do not need to use any of the classes of this assembly (unless customizing t HttpClientExtensions.cs + + Drawing\SystemIconsHandler + + + ShellIcon.cs + diff --git a/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs index e9c654b5bf..a37c3e0de3 100644 --- a/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/TreeGridViewHandler.cs @@ -346,7 +346,7 @@ void AutoSizeColumns(bool displayedOnly) var mode = displayedOnly ? swf.DataGridViewAutoSizeColumnMode.DisplayedCells : swf.DataGridViewAutoSizeColumnMode.AllCells; var width = colHandler.Control.GetPreferredWidth(mode, false); if (width > colHandler.Control.Width) - colHandler.Control.Width = width; + colHandler.Control.Width = Math.Min(ushort.MaxValue, width); } } } diff --git a/src/Eto.WinForms/IO/ShellIcon.cs b/src/Eto.WinForms/IO/ShellIcon.cs deleted file mode 100644 index fc86c4cf74..0000000000 --- a/src/Eto.WinForms/IO/ShellIcon.cs +++ /dev/null @@ -1,240 +0,0 @@ -#if WINFORMS -namespace Eto.WinForms.IO -#elif WPF -namespace Eto.Wpf.IO -#endif -{ - - /// - /// Summary description for Shell. - /// - public static class ShellIcon - { - /// - /// Options to specify the size of icons to return. - /// - public enum IconSize - { - /// - /// Specify large icon - 32 pixels by 32 pixels. - /// - Large = 0, - /// - /// Specify small icon - 16 pixels by 16 pixels. - /// - Small = 1 - } - - /// - /// Options to specify whether folders should be in the open or closed state. - /// - public enum FolderType - { - /// - /// Specify open folder. - /// - Open = 0, - /// - /// Specify closed folder. - /// - Closed = 1 - } - - public static System.Drawing.Icon GetFileIcon(string name, IconSize size, bool linkOverlay) - { - var shfi = new Shell32.SHFILEINFO(); - uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES; - - if (linkOverlay) flags |= Shell32.SHGFI_LINKOVERLAY; - - - /* Check the size specified for return. */ - if (IconSize.Small == size) - { - flags |= Shell32.SHGFI_SMALLICON ; // include the small icon flag - } - else - { - flags |= Shell32.SHGFI_LARGEICON ; // include the large icon flag - } - - Shell32.SHGetFileInfo( name, - Shell32.FILE_ATTRIBUTE_NORMAL, - ref shfi, - (uint) Marshal.SizeOf(shfi), - flags ); - - - // Copy (clone) the returned icon to a new object, thus allowing us - // to call DestroyIcon immediately - if (shfi.hIcon == IntPtr.Zero) - { - return null; - } - else - { - var icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone(); - User32.DestroyIcon( shfi.hIcon ); // Cleanup - return icon; - } - } - /// - /// Used to access system folder icons. - /// - /// Specify large or small icons. - /// Specify open or closed FolderType. - /// System.Drawing.Icon - public static System.Drawing.Icon GetFolderIcon( IconSize size, FolderType folderType ) - { - // Need to add size check, although errors generated at present! - uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES; - - if (FolderType.Open == folderType) - { - flags |= Shell32.SHGFI_OPENICON; - } - - if (IconSize.Small == size) - { - flags |= Shell32.SHGFI_SMALLICON; - } - else - { - flags |= Shell32.SHGFI_LARGEICON; - } - - // Get the folder icon - var shfi = new Shell32.SHFILEINFO(); - Shell32.SHGetFileInfo( null, - Shell32.FILE_ATTRIBUTE_DIRECTORY, - ref shfi, - (uint) Marshal.SizeOf(shfi), - flags ); - - System.Drawing.Icon.FromHandle(shfi.hIcon); // Load the icon from an HICON handle - - // Now clone the icon, so that it can be successfully stored in an ImageList - var icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone(); - - User32.DestroyIcon( shfi.hIcon ); // Cleanup - return icon; - } - - } - - /// - /// Wraps necessary Shell32.dll structures and functions required to retrieve Icon Handles using SHGetFileInfo. Code - /// courtesy of MSDN Cold Rooster Consulting case study. - /// - /// - - // This code has been left largely untouched from that in the CRC example. The main changes have been moving - // the icon reading code over to the IconReader type. - public static class Shell32 - { - // Analysis disable InconsistentNaming - public const int MAX_PATH = 256; - [StructLayout(LayoutKind.Sequential)] - public struct SHITEMID - { - public ushort cb; - [MarshalAs(UnmanagedType.LPArray)] - public byte[] abID; - } - - [StructLayout(LayoutKind.Sequential)] - public struct ITEMIDLIST - { - public SHITEMID mkid; - } - - [StructLayout(LayoutKind.Sequential)] - public struct BROWSEINFO - { - public IntPtr hwndOwner; - public IntPtr pidlRoot; - public IntPtr pszDisplayName; - [MarshalAs(UnmanagedType.LPTStr)] - public string lpszTitle; - public uint ulFlags; - public IntPtr lpfn; - public int lParam; - public IntPtr iImage; - } - - // Browsing for directory. - public const uint BIF_RETURNONLYFSDIRS = 0x0001; - public const uint BIF_DONTGOBELOWDOMAIN = 0x0002; - public const uint BIF_STATUSTEXT = 0x0004; - public const uint BIF_RETURNFSANCESTORS = 0x0008; - public const uint BIF_EDITBOX = 0x0010; - public const uint BIF_VALIDATE = 0x0020; - public const uint BIF_NEWDIALOGSTYLE = 0x0040; - public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX); - public const uint BIF_BROWSEINCLUDEURLS = 0x0080; - public const uint BIF_BROWSEFORCOMPUTER = 0x1000; - public const uint BIF_BROWSEFORPRINTER = 0x2000; - public const uint BIF_BROWSEINCLUDEFILES = 0x4000; - public const uint BIF_SHAREABLE = 0x8000; - - [StructLayout(LayoutKind.Sequential)] - public struct SHFILEINFO - { - public const int NAMESIZE = 80; - public IntPtr hIcon; - public int iIcon; - public uint dwAttributes; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)] - public string szDisplayName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)] - public string szTypeName; - }; - - public const uint SHGFI_ICON = 0x000000100; // get icon - public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name - public const uint SHGFI_TYPENAME = 0x000000400; // get type name - public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes - public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location - public const uint SHGFI_EXETYPE = 0x000002000; // return exe type - public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index - public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon - public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state - public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes - public const uint SHGFI_LARGEICON = 0x000000000; // get large icon - public const uint SHGFI_SMALLICON = 0x000000001; // get small icon - public const uint SHGFI_OPENICON = 0x000000002; // get open icon - public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon - public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl - public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute - public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays - public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay - - public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; - public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; - - [DllImport("Shell32.dll")] - public static extern IntPtr SHGetFileInfo( - string pszPath, - uint dwFileAttributes, - ref SHFILEINFO psfi, - uint cbFileInfo, - uint uFlags - ); - } - - /// - /// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example. - /// - public static class User32 - { - /// - /// Provides access to function required to delete handle. This method is used internally - /// and is not required to be called separately. - /// - /// Pointer to icon handle. - /// N/A - [DllImport("User32.dll")] - public static extern int DestroyIcon( IntPtr hIcon ); - } - -} diff --git a/src/Eto.WinForms/IO/SystemIcons.cs b/src/Eto.WinForms/IO/SystemIcons.cs deleted file mode 100644 index 9283ca62f5..0000000000 --- a/src/Eto.WinForms/IO/SystemIcons.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Eto.IO; -using Eto.WinForms.Drawing; -namespace Eto.WinForms.IO -{ - public class SystemIconsHandler : SystemIcons.IHandler - { - public Icon GetFileIcon(string fileName, IconSize size) - { - ShellIcon.IconSize iconSize; - switch (size) - { - case IconSize.Large: - iconSize = ShellIcon.IconSize.Large; - break; - case IconSize.Small: - iconSize = ShellIcon.IconSize.Small; - break; - default: - throw new NotSupportedException(); - } - - sd.Icon icon = ShellIcon.GetFileIcon(fileName, iconSize, false); - return new Icon(new IconHandler(icon)); - } - - public Icon GetStaticIcon(StaticIconType type, IconSize size) - { - ShellIcon.IconSize iconSize; - switch (size) - { - case IconSize.Large: - iconSize = ShellIcon.IconSize.Large; - break; - case IconSize.Small: - iconSize = ShellIcon.IconSize.Small; - break; - default: - throw new NotSupportedException(); - } - - ShellIcon.FolderType folderType; - switch (type) - { - case StaticIconType.OpenDirectory: - folderType = ShellIcon.FolderType.Open; - break; - case StaticIconType.CloseDirectory: - folderType = ShellIcon.FolderType.Closed; - break; - default: - throw new NotSupportedException(); - } - - sd.Icon icon = ShellIcon.GetFolderIcon(iconSize, folderType); - return new Icon(new IconHandler(icon)); - } - } -} diff --git a/src/Eto.WinForms/Platform.cs b/src/Eto.WinForms/Platform.cs index 7a894871ba..b83b7d0c38 100644 --- a/src/Eto.WinForms/Platform.cs +++ b/src/Eto.WinForms/Platform.cs @@ -1,9 +1,7 @@ -using Eto.IO; using Eto.WinForms.Drawing; using Eto.WinForms.Forms; using Eto.WinForms.Forms.Printing; using Eto.WinForms.Forms.Controls; -using Eto.WinForms.IO; using Eto.Forms.ThemedControls; using Eto.WinForms.Forms.Cells; using Eto.WinForms.Forms.Menu; diff --git a/src/Eto.WinForms/Win32.cs b/src/Eto.WinForms/Win32.cs index 15d73fcf52..a6981f2c50 100755 --- a/src/Eto.WinForms/Win32.cs +++ b/src/Eto.WinForms/Win32.cs @@ -604,7 +604,7 @@ public enum SBOrientation : int SB_CTL = 0x2, SB_BOTH = 0x3 } - + [Serializable, StructLayout(LayoutKind.Sequential)] public struct SCROLLINFO { @@ -629,11 +629,144 @@ public struct SCROLLINFO [DllImport("user32.dll")] public static extern bool DestroyWindow(IntPtr hWnd); - + [DllImport("User32.dll", SetLastError = true)] - public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); + public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); [DllImport("kernel32.dll")] public static extern void SetLastError(uint dwErrCode); + + public const int MAX_PATH = 260; + + [Flags] + public enum SHGSI : uint + { + SHGSI_ICONLOCATION = 0, + SHGSI_ICON = 0x000000100, + SHGSI_SYSICONINDEX = 0x000004000, + SHGSI_LINKOVERLAY = 0x000008000, + SHGSI_SELECTED = 0x000010000, + SHGSI_LARGEICON = 0x000000000, + SHGSI_SMALLICON = 0x000000001, + SHGSI_SHELLICONSIZE = 0x000000004 + } + public enum SHSTOCKICONID : uint + { + SIID_DOCNOASSOC = 0, //Blank document icon (Document of a type with no associated application). + SIID_DOCASSOC = 1, //Application-associated document icon (Document of a type with an associated application). + SIID_APPLICATION = 2, //Generic application with no custom icon. + SIID_FOLDER = 3, //Folder (generic, unspecified state). + SIID_FOLDEROPEN = 4, //Folder (open). + SIID_DRIVE525 = 5, //5.25-inch disk drive. + SIID_DRIVE35 = 6, //3.5-inch disk drive. + SIID_DRIVEREMOVE = 7, //Removable drive. + SIID_DRIVEFIXED = 8, //Fixed drive (hard disk). + SIID_DRIVENET = 9, //Network drive (connected). + SIID_DRIVENETDISABLED = 10, //Network drive (disconnected). + SIID_DRIVECD = 11, //CD drive. + SIID_DRIVERAM = 12, //RAM disk drive. + SIID_WORLD = 13, //The entire network. + SIID_SERVER = 15, //A computer on the network. + SIID_PRINTER = 16, //A local printer or print destination. + SIID_MYNETWORK = 17, //The Network virtual folder (FOLDERID_NetworkFolder/CSIDL_NETWORK). + SIID_FIND = 22, //The Search feature. + SIID_HELP = 23, //The Help and Support feature. + SIID_SHARE = 28, //Overlay for a shared item. + SIID_LINK = 29, //Overlay for a shortcut. + SIID_SLOWFILE = 30, //Overlay for items that are expected to be slow to access. + SIID_RECYCLER = 31, //The Recycle Bin (empty). + SIID_RECYCLERFULL = 32, //The Recycle Bin (not empty). + SIID_MEDIACDAUDIO = 40, //Audio CD media. + SIID_LOCK = 47, //Security lock. + SIID_AUTOLIST = 49, //A virtual folder that contains the results of a search. + SIID_PRINTERNET = 50, //A network printer. + SIID_SERVERSHARE = 51, //A server shared on a network. + SIID_PRINTERFAX = 52, //A local fax printer. + SIID_PRINTERFAXNET = 53, //A network fax printer. + SIID_PRINTERFILE = 54, //A file that receives the output of a Print to file operation. + SIID_STACK = 55, //A category that results from a Stack by command to organize the contents of a folder. + SIID_MEDIASVCD = 56, //Super Video CD (SVCD) media. + SIID_STUFFEDFOLDER = 57, //A folder that contains only subfolders as child items. + SIID_DRIVEUNKNOWN = 58, //Unknown drive type. + SIID_DRIVEDVD = 59, //DVD drive. + SIID_MEDIADVD = 60, //DVD media. + SIID_MEDIADVDRAM = 61, //DVD-RAM media. + SIID_MEDIADVDRW = 62, //DVD-RW media. + SIID_MEDIADVDR = 63, //DVD-R media. + SIID_MEDIADVDROM = 64, //DVD-ROM media. + SIID_MEDIACDAUDIOPLUS = 65, //CD+ (enhanced audio CD) media. + SIID_MEDIACDRW = 66, //CD-RW media. + SIID_MEDIACDR = 67, //CD-R media. + SIID_MEDIACDBURN = 68, //A writeable CD in the process of being burned. + SIID_MEDIABLANKCD = 69, //Blank writable CD media. + SIID_MEDIACDROM = 70, //CD-ROM media. + SIID_AUDIOFILES = 71, //An audio file. + SIID_IMAGEFILES = 72, //An image file. + SIID_VIDEOFILES = 73, //A video file. + SIID_MIXEDFILES = 74, //A mixed file. + SIID_FOLDERBACK = 75, //Folder back. + SIID_FOLDERFRONT = 76, //Folder front. + SIID_SHIELD = 77, //Security shield. Use for UAC prompts only. + SIID_WARNING = 78, //Warning. + SIID_INFO = 79, //Informational. + SIID_ERROR = 80, //Error. + SIID_KEY = 81, //Key. + SIID_SOFTWARE = 82, //Software. + SIID_RENAME = 83, //A UI item, such as a button, that issues a rename command. + SIID_DELETE = 84, //A UI item, such as a button, that issues a delete command. + SIID_MEDIAAUDIODVD = 85, //Audio DVD media. + SIID_MEDIAMOVIEDVD = 86, //Movie DVD media. + SIID_MEDIAENHANCEDCD = 87, //Enhanced CD media. + SIID_MEDIAENHANCEDDVD = 88, //Enhanced DVD media. + SIID_MEDIAHDDVD = 89, //High definition DVD media in the HD DVD format. + SIID_MEDIABLURAY = 90, //High definition DVD media in the Blu-ray Discâ„¢ format. + SIID_MEDIAVCD = 91, //Video CD (VCD) media. + SIID_MEDIADVDPLUSR = 92, //DVD+R media. + SIID_MEDIADVDPLUSRW = 93, //DVD+RW media. + SIID_DESKTOPPC = 94, //A desktop computer. + SIID_MOBILEPC = 95, //A mobile computer (laptop). + SIID_USERS = 96, //The User Accounts Control Panel item. + SIID_MEDIASMARTMEDIA = 97, //Smart media. + SIID_MEDIACOMPACTFLASH = 98, //CompactFlash media. + SIID_DEVICECELLPHONE = 99, //A cell phone. + SIID_DEVICECAMERA = 100, //A digital camera. + SIID_DEVICEVIDEOCAMERA = 101, //A digital video camera. + SIID_DEVICEAUDIOPLAYER = 102, //An audio player. + SIID_NETWORKCONNECT = 103, //Connect to network. + SIID_INTERNET = 104, //The Network and Internet Control Panel item. + SIID_ZIPFILE = 105, //A compressed file with a .zip file name extension. + SIID_SETTINGS = 106, //The Additional Options Control Panel item. + SIID_DRIVEHDDVD = 132, //Windows Vista with Service Pack 1 (SP1) and later. High definition DVD drive (any type - HD DVD-ROM, HD DVD-R, HD-DVD-RAM) that uses the HD DVD format. + SIID_DRIVEBD = 133, //Windows Vista with SP1 and later. High definition DVD drive (any type - BD-ROM, BD-R, BD-RE) that uses the Blu-ray Disc format. + SIID_MEDIAHDDVDROM = 134, //Windows Vista with SP1 and later. High definition DVD-ROM media in the HD DVD-ROM format. + SIID_MEDIAHDDVDR = 135, //Windows Vista with SP1 and later. High definition DVD-R media in the HD DVD-R format. + SIID_MEDIAHDDVDRAM = 136, //Windows Vista with SP1 and later. High definition DVD-RAM media in the HD DVD-RAM format. + SIID_MEDIABDROM = 137, //Windows Vista with SP1 and later. High definition DVD-ROM media in the Blu-ray Disc BD-ROM format. + SIID_MEDIABDR = 138, //Windows Vista with SP1 and later. High definition write-once media in the Blu-ray Disc BD-R format. + SIID_MEDIABDRE = 139, //Windows Vista with SP1 and later. High definition read/write media in the Blu-ray Disc BD-RE format. + SIID_CLUSTEREDDRIVE = 140, //Windows Vista with SP1 and later. A cluster disk array. + SIID_MAX_ICONS = 174, //The highest valid value in the enumeration. Values over 160 are Windows 7-only icons. + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SHSTOCKICONINFO + { + public UInt32 cbSize; + public IntPtr hIcon; + public Int32 iSysIconIndex; + public Int32 iIcon; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] + public string szPath; + } + [DllImport("Shell32.dll", SetLastError = false)] + public static extern Int32 SHGetStockIconInfo(SHSTOCKICONID siid, SHGSI uFlags, ref SHSTOCKICONINFO psii); + + /// + /// Provides access to function required to delete handle. This method is used internally + /// and is not required to be called separately. + /// + /// Pointer to icon handle. + /// N/A + [DllImport("User32.dll")] + public static extern int DestroyIcon(IntPtr hIcon); } } diff --git a/src/Eto.Wpf/Drawing/FormattedTextHandler.cs b/src/Eto.Wpf/Drawing/FormattedTextHandler.cs index ca760a0edf..2468554c51 100644 --- a/src/Eto.Wpf/Drawing/FormattedTextHandler.cs +++ b/src/Eto.Wpf/Drawing/FormattedTextHandler.cs @@ -66,7 +66,7 @@ public Font Font } public Brush ForegroundBrush { - get => _foregroundBrush; + get => _foregroundBrush ?? Brushes.Black; set { _foregroundBrush = value; diff --git a/src/Eto.Wpf/Drawing/SystemIconsHandler.cs b/src/Eto.Wpf/Drawing/SystemIconsHandler.cs new file mode 100644 index 0000000000..b62bebe9a6 --- /dev/null +++ b/src/Eto.Wpf/Drawing/SystemIconsHandler.cs @@ -0,0 +1,69 @@ +#if WPF +namespace Eto.Wpf.Drawing; +#elif WINFORMS +namespace Eto.WinForms.Drawing; +#endif + +public class SystemIconsHandler : SystemIcons.IHandler +{ + public Icon GetFileIcon(string fileName, SystemIconSize size) + { + var iconSize = size switch + { + SystemIconSize.Large => ShellIcon.IconSize.Large, + SystemIconSize.Small => ShellIcon.IconSize.Small, + _ => throw new NotSupportedException(), + }; + + using (var icon = ShellIcon.GetFileIcon(fileName, iconSize, false)) + { + return WithSize(new Icon(new IconHandler(icon)), size); + } + } + + public Icon Get(SystemIconType type, SystemIconSize size) + { + return type switch + { + SystemIconType.OpenDirectory => GetSystemIcon(Win32.SHSTOCKICONID.SIID_FOLDEROPEN, size), + SystemIconType.CloseDirectory => GetSystemIcon(Win32.SHSTOCKICONID.SIID_FOLDER, size), + SystemIconType.Question => GetSystemIcon(Win32.SHSTOCKICONID.SIID_HELP, size), + SystemIconType.Error => GetSystemIcon(Win32.SHSTOCKICONID.SIID_ERROR, size), + SystemIconType.Information => GetSystemIcon(Win32.SHSTOCKICONID.SIID_INFO, size), + SystemIconType.Warning => GetSystemIcon(Win32.SHSTOCKICONID.SIID_WARNING, size), + _ => throw new NotSupportedException(), + }; + + } + + Icon GetSystemIcon(Win32.SHSTOCKICONID icon_id, SystemIconSize size) + { + Win32.SHGSI flags = Win32.SHGSI.SHGSI_ICON; + flags |= size switch + { + SystemIconSize.Large => Win32.SHGSI.SHGSI_LARGEICON, + SystemIconSize.Small => Win32.SHGSI.SHGSI_SMALLICON, + _ => throw new NotSupportedException(), + }; + var sii = new Win32.SHSTOCKICONINFO(); + sii.cbSize = (UInt32)Marshal.SizeOf(typeof(Win32.SHSTOCKICONINFO)); + Win32.SHGetStockIconInfo(icon_id, flags, ref sii); + var sdicon = (sd.Icon)sd.Icon.FromHandle(sii.hIcon).Clone(); + Win32.DestroyIcon(sii.hIcon); + var icon = new Icon(new IconHandler(sdicon)); + return WithSize(icon, size); + } + + private static Icon WithSize(Icon icon, SystemIconSize size) + { + var pixels = size switch + { + SystemIconSize.Large => 32, + SystemIconSize.Small => 16, + _ => throw new NotSupportedException() + }; + icon = icon.WithSize(pixels, pixels); + return icon; + } + +} diff --git a/src/Eto.Wpf/Eto.Wpf.csproj b/src/Eto.Wpf/Eto.Wpf.csproj index 435fc8aa6b..22a881abdb 100755 --- a/src/Eto.Wpf/Eto.Wpf.csproj +++ b/src/Eto.Wpf/Eto.Wpf.csproj @@ -118,9 +118,6 @@ You do not need to use any of the classes of this assembly (unless customizing t True FontDialogResources.resx - - IO\ShellIcon.cs - EmbeddedAssemblyLoader.cs diff --git a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs index 35e9e8084c..43ab8a9a92 100755 --- a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs +++ b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs @@ -305,7 +305,7 @@ public virtual sw.Size GetPreferredSize(sw.Size constraint) return ContainerControl.DesiredSize; } - public SizeF GetPreferredSize(SizeF availableSize) + public virtual SizeF GetPreferredSize(SizeF availableSize) { var size = availableSize.ToWpf(); if (ContainerControl.Parent == null && !(ContainerControl is swc.Panel) && !(ContainerControl is swc.Border)) diff --git a/src/Eto.Wpf/Forms/WpfWindow.cs b/src/Eto.Wpf/Forms/WpfWindow.cs index fd38d235bf..cd94651805 100755 --- a/src/Eto.Wpf/Forms/WpfWindow.cs +++ b/src/Eto.Wpf/Forms/WpfWindow.cs @@ -28,6 +28,7 @@ static class WpfWindow internal static readonly object Closeable_Key = new object(); internal static readonly object Resizable_Key = new object(); internal static readonly object Icon_Key = new object(); + internal static readonly object ShowSystemMenu_Key = new object(); } public abstract class WpfWindow : WpfPanel, Window.IHandler, IWpfWindow, IInputBindingHost @@ -471,6 +472,25 @@ public MenuBar Menu } } + public override SizeF GetPreferredSize(SizeF availableSize) + { + var _ = NativeHandle; + var old = Control.SizeToContent; + if (!Control.IsLoaded) + Control.SizeToContent = sw.SizeToContent.WidthAndHeight; + var available = availableSize.ToWpf(); + var preferred = UserPreferredSize; + if (!double.IsNaN(preferred.Width)) + available.Width = preferred.Width; + if (!double.IsNaN(preferred.Height)) + available.Width = preferred.Height; + Control.ApplyAllTemplates(); + Control.Measure(available); + var desired = SizeF.Max(MinimumSize, Control.DesiredSize.ToEto()); + Control.SizeToContent = old; + return desired; + } + public Icon Icon { get => Widget.Properties.Get(WpfWindow.Icon_Key, () => { @@ -538,10 +558,21 @@ public virtual bool Closeable } } + public bool? ShowSystemMenu + { + get => Widget.Properties.Get(WpfWindow.ShowSystemMenu_Key); + set + { + if (Widget.Properties.TrySet(WpfWindow.ShowSystemMenu_Key, value)) + SetSystemMenu(); + } + } + + void SetSystemMenu() { // hide system menu (and close button) if all commands are disabled - var useSystemMenu = Closeable || Minimizable || Maximizable; + var useSystemMenu = ShowSystemMenu ?? (Closeable || Minimizable || Maximizable); SetStyle(Win32.WS.SYSMENU, useSystemMenu); // enable/disable the close button if shown (does not disable Alt+F4) diff --git a/src/Eto.Wpf/IO/SystemIcons.cs b/src/Eto.Wpf/IO/SystemIcons.cs deleted file mode 100644 index e0c4e6198b..0000000000 --- a/src/Eto.Wpf/IO/SystemIcons.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Eto.IO; -using Eto.Wpf.Drawing; - -namespace Eto.Wpf.IO -{ - public class SystemIconsHandler : SystemIcons.IHandler - { - public Icon GetFileIcon (string fileName, IconSize size) - { - ShellIcon.IconSize iconSize; - switch (size) { - case IconSize.Large: - iconSize = ShellIcon.IconSize.Large; - break; - case IconSize.Small: - iconSize = ShellIcon.IconSize.Small; - break; - default: - throw new NotSupportedException (); - } - - using (var icon = ShellIcon.GetFileIcon (fileName, iconSize, false)) { - return new Icon(new IconHandler(icon)); - } - } - - public Icon GetStaticIcon (StaticIconType type, IconSize size) - { - ShellIcon.IconSize iconSize; - switch (size) { - case IconSize.Large: - iconSize = ShellIcon.IconSize.Large; - break; - case IconSize.Small: - iconSize = ShellIcon.IconSize.Small; - break; - default: - throw new NotSupportedException (); - } - - ShellIcon.FolderType folderType; - switch (type) { - case StaticIconType.OpenDirectory: - folderType = ShellIcon.FolderType.Open; - break; - case StaticIconType.CloseDirectory: - folderType = ShellIcon.FolderType.Closed; - break; - default: - throw new NotSupportedException (); - } - - using (var icon = ShellIcon.GetFolderIcon (iconSize, folderType)) { - return new Icon(new IconHandler(icon)); - } - } - } -} diff --git a/src/Eto.Wpf/Platform.cs b/src/Eto.Wpf/Platform.cs index d0f9e6aedb..dcdb2c47cd 100755 --- a/src/Eto.Wpf/Platform.cs +++ b/src/Eto.Wpf/Platform.cs @@ -5,8 +5,6 @@ using Eto.Wpf.Forms.Controls; using Eto.Wpf.Forms.Printing; using Eto.Wpf.Forms; -using Eto.IO; -using Eto.Wpf.IO; using Eto.Forms.ThemedControls; using Eto.Shared.Forms; namespace Eto.Wpf @@ -53,6 +51,17 @@ static Platform() } }; }); + + Style.Add(null, c => + { + c.Load += (sender, e) => + { + if (c.ControlObject is sw.Window window) + { + window.WindowStartupLocation = sw.WindowStartupLocation.CenterScreen; + } + }; + }); } public Platform() diff --git a/src/Eto.Wpf/ShellIcon.cs b/src/Eto.Wpf/ShellIcon.cs new file mode 100644 index 0000000000..a5e21c2b02 --- /dev/null +++ b/src/Eto.Wpf/ShellIcon.cs @@ -0,0 +1,220 @@ +#if WINFORMS +namespace Eto.WinForms; +#elif WPF +namespace Eto.Wpf; +#endif + +/// +/// Summary description for Shell. +/// +static class ShellIcon +{ + /// + /// Options to specify the size of icons to return. + /// + public enum IconSize + { + /// + /// Specify large icon - 32 pixels by 32 pixels. + /// + Large = 0, + /// + /// Specify small icon - 16 pixels by 16 pixels. + /// + Small = 1 + } + + /// + /// Options to specify whether folders should be in the open or closed state. + /// + public enum FolderType + { + /// + /// Specify open folder. + /// + Open = 0, + /// + /// Specify closed folder. + /// + Closed = 1 + } + + public static System.Drawing.Icon GetFileIcon(string name, IconSize size, bool linkOverlay) + { + var shfi = new Shell32.SHFILEINFO(); + uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES; + + if (linkOverlay) flags |= Shell32.SHGFI_LINKOVERLAY; + + + /* Check the size specified for return. */ + if (IconSize.Small == size) + { + flags |= Shell32.SHGFI_SMALLICON; // include the small icon flag + } + else + { + flags |= Shell32.SHGFI_LARGEICON; // include the large icon flag + } + + Shell32.SHGetFileInfo(name, + Shell32.FILE_ATTRIBUTE_NORMAL, + ref shfi, + (uint)Marshal.SizeOf(shfi), + flags); + + + // Copy (clone) the returned icon to a new object, thus allowing us + // to call DestroyIcon immediately + if (shfi.hIcon == IntPtr.Zero) + { + return null; + } + else + { + var icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone(); + Win32.DestroyIcon(shfi.hIcon); // Cleanup + return icon; + } + } + /// + /// Used to access system folder icons. + /// + /// Specify large or small icons. + /// Specify open or closed FolderType. + /// System.Drawing.Icon + public static System.Drawing.Icon GetFolderIcon(IconSize size, FolderType folderType) + { + // Need to add size check, although errors generated at present! + uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES; + + if (FolderType.Open == folderType) + { + flags |= Shell32.SHGFI_OPENICON; + } + + if (IconSize.Small == size) + { + flags |= Shell32.SHGFI_SMALLICON; + } + else + { + flags |= Shell32.SHGFI_LARGEICON; + } + + // Get the folder icon + var shfi = new Shell32.SHFILEINFO(); + Shell32.SHGetFileInfo(null, + Shell32.FILE_ATTRIBUTE_DIRECTORY, + ref shfi, + (uint)Marshal.SizeOf(shfi), + flags); + + // Now clone the icon, so that it can be successfully stored in an ImageList + var icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone(); + + Win32.DestroyIcon(shfi.hIcon); // Cleanup + return icon; + } + +} + +/// +/// Wraps necessary Shell32.dll structures and functions required to retrieve Icon Handles using SHGetFileInfo. Code +/// courtesy of MSDN Cold Rooster Consulting case study. +/// +/// + +// This code has been left largely untouched from that in the CRC example. The main changes have been moving +// the icon reading code over to the IconReader type. +static class Shell32 +{ + // Analysis disable InconsistentNaming + public const int MAX_PATH = 256; + [StructLayout(LayoutKind.Sequential)] + public struct SHITEMID + { + public ushort cb; + [MarshalAs(UnmanagedType.LPArray)] + public byte[] abID; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ITEMIDLIST + { + public SHITEMID mkid; + } + + [StructLayout(LayoutKind.Sequential)] + public struct BROWSEINFO + { + public IntPtr hwndOwner; + public IntPtr pidlRoot; + public IntPtr pszDisplayName; + [MarshalAs(UnmanagedType.LPTStr)] + public string lpszTitle; + public uint ulFlags; + public IntPtr lpfn; + public int lParam; + public IntPtr iImage; + } + + // Browsing for directory. + public const uint BIF_RETURNONLYFSDIRS = 0x0001; + public const uint BIF_DONTGOBELOWDOMAIN = 0x0002; + public const uint BIF_STATUSTEXT = 0x0004; + public const uint BIF_RETURNFSANCESTORS = 0x0008; + public const uint BIF_EDITBOX = 0x0010; + public const uint BIF_VALIDATE = 0x0020; + public const uint BIF_NEWDIALOGSTYLE = 0x0040; + public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX); + public const uint BIF_BROWSEINCLUDEURLS = 0x0080; + public const uint BIF_BROWSEFORCOMPUTER = 0x1000; + public const uint BIF_BROWSEFORPRINTER = 0x2000; + public const uint BIF_BROWSEINCLUDEFILES = 0x4000; + public const uint BIF_SHAREABLE = 0x8000; + + [StructLayout(LayoutKind.Sequential)] + public struct SHFILEINFO + { + public const int NAMESIZE = 80; + public IntPtr hIcon; + public int iIcon; + public uint dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] + public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)] + public string szTypeName; + }; + + public const uint SHGFI_ICON = 0x000000100; // get icon + public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name + public const uint SHGFI_TYPENAME = 0x000000400; // get type name + public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes + public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location + public const uint SHGFI_EXETYPE = 0x000002000; // return exe type + public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index + public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon + public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state + public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes + public const uint SHGFI_LARGEICON = 0x000000000; // get large icon + public const uint SHGFI_SMALLICON = 0x000000001; // get small icon + public const uint SHGFI_OPENICON = 0x000000002; // get open icon + public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon + public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl + public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute + public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays + public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay + + public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; + + [DllImport("Shell32.dll")] + public static extern IntPtr SHGetFileInfo( + string pszPath, + uint dwFileAttributes, + ref SHFILEINFO psfi, + uint cbFileInfo, + uint uFlags + ); +} \ No newline at end of file diff --git a/src/Eto.Wpf/WpfConversions.cs b/src/Eto.Wpf/WpfConversions.cs index 87bc72dd4c..356fc13401 100755 --- a/src/Eto.Wpf/WpfConversions.cs +++ b/src/Eto.Wpf/WpfConversions.cs @@ -838,13 +838,9 @@ public static void SetEtoBorderType(this swc.Control control, BorderType value, } } - public static Image ToEto(this System.Drawing.Icon icon) + public static Icon ToEto(this System.Drawing.Icon icon) { - var imageSource = Imaging.CreateBitmapSourceFromHIcon( - icon.Handle, - sw.Int32Rect.Empty, - swmi.BitmapSizeOptions.FromEmptyOptions()); - return imageSource.ToEto(); + return new Icon(new IconHandler(icon)); } public static Icon ToEtoIcon(this swm.ImageSource bitmap) diff --git a/src/Eto/Drawing/SystemIcons.cs b/src/Eto/Drawing/SystemIcons.cs new file mode 100644 index 0000000000..c695537594 --- /dev/null +++ b/src/Eto/Drawing/SystemIcons.cs @@ -0,0 +1,144 @@ +namespace Eto.Drawing; + +/// +/// Type of system icon to get +/// +/// (c) 2014 by Curtis Wensley +/// See LICENSE for full terms +public enum SystemIconType +{ + /// + /// Icon for an open directory/folder + /// + OpenDirectory, + /// + /// Icon for a closed directory/folder + /// + CloseDirectory, + /// + /// Icon for a question mark + /// + Question, + /// + /// Icon for errors + /// + Error, + /// + /// Icon to use for informational messages + /// + Information, + /// + /// Icon to use for warnings + /// + Warning + +} + +/// +/// Size of icon to get +/// +/// (c) 2014 by Curtis Wensley +/// See LICENSE for full terms +public enum SystemIconSize +{ + /// + /// Large icon (usually suitable to display at 32x32 logical pixels) + /// + Large, + /// + /// Small icon (usually suitable to display at 16x16 logical pixels) + /// + Small +} + +/// +/// Methods to get system icons for file types and static icons +/// +/// (c) 2014 by Curtis Wensley +/// See LICENSE for full terms +[Handler(typeof(IHandler))] +public static class SystemIcons +{ + static IHandler Handler => Platform.Instance.CreateShared(); + + static readonly object cacheKey = new object(); + + static Dictionary GetLookupTable(SystemIconSize size) + { + var htSizes = Platform.Instance.Cache>(cacheKey); + Dictionary htIcons; + if (!htSizes.TryGetValue(size, out htIcons)) + { + htIcons = new Dictionary(); + htSizes.Add(size, htIcons); + } + return htIcons; + } + + /// + /// Gets a file icon for the specified file + /// + /// + /// The file does not necessarily have to exist for the icon to be retrieved, though if it is a specific file + /// then the platform may be able to return a file-specific icon if one is available. + /// + /// The icon for the specified file name. + /// Name of the file to get the icon for. + /// Size of the icon. + public static Icon GetFileIcon(string fileName, SystemIconSize size) + { + var htIcons = GetLookupTable(size); + string ext = Path.GetExtension(fileName).ToUpperInvariant(); + Icon icon; + if (!htIcons.TryGetValue(ext, out icon)) + { + icon = Handler.GetFileIcon(fileName, size); + htIcons.Add(ext, icon); + } + return icon; + } + + /// + /// Gets a static system-defined icon for the specified type. + /// + /// The static icon for the specified type. + /// Type of the static icon to retrieve. + /// Size of the icon. + public static Icon Get(SystemIconType type, SystemIconSize size) + { + var htIcons = GetLookupTable(size); + Icon icon; + if (!htIcons.TryGetValue(type, out icon)) + { + icon = Handler.Get(type, size); + htIcons.Add(type, icon); + } + return icon; + } + + /// + /// Handler interface for the methods + /// + public interface IHandler + { + /// + /// Gets a file icon for the specified file + /// + /// + /// The file does not necessarily have to exist for the icon to be retrieved, though if it is a specific file + /// then the platform may be able to return a file-specific icon if one is available. + /// + /// The icon for the specified file name. + /// Name of the file to get the icon for. + /// Size of the icon. + Icon GetFileIcon(string fileName, SystemIconSize size); + + /// + /// Gets a static system-defined icon for the specified type. + /// + /// The static icon for the specified type. + /// Type of the static icon to retrieve. + /// Size of the icon. + Icon Get(SystemIconType type, SystemIconSize size); + } +} \ No newline at end of file diff --git a/src/Eto/Forms/ThemedControls/ThemedMessageBoxHandler.cs b/src/Eto/Forms/ThemedControls/ThemedMessageBoxHandler.cs index 9eb18bbe93..ff2c119bfe 100755 --- a/src/Eto/Forms/ThemedControls/ThemedMessageBoxHandler.cs +++ b/src/Eto/Forms/ThemedControls/ThemedMessageBoxHandler.cs @@ -24,29 +24,52 @@ public class ThemedMessageBoxHandler : WidgetHandler, MessageBox.IHandle public DialogResult ShowDialog(Control parent) { var dlg = new ThemedMessageBox(); - dlg.Title = Caption; + dlg.Title = Caption ?? parent?.ParentWindow?.Title ?? Application.Instance.Localize(Widget, "Error"); dlg.Text = Text; // todo: add ability to get icons needed from SystemIcons - //dlg.Image = GetImage(); + dlg.Image = GetImage(); + + var defaultButton = DefaultButton; + if (defaultButton == MessageBoxDefaultButton.Default) + { + switch (Buttons) + { + case MessageBoxButtons.OK: + defaultButton = MessageBoxDefaultButton.OK; + break; + case MessageBoxButtons.OKCancel: + defaultButton = MessageBoxDefaultButton.Cancel; + break; + case MessageBoxButtons.YesNo: + defaultButton = MessageBoxDefaultButton.No; + break; + case MessageBoxButtons.YesNoCancel: + defaultButton = MessageBoxDefaultButton.Cancel; + break; + default: + throw new NotSupportedException(); + } + } + var app = Application.Instance; switch (Buttons) { case MessageBoxButtons.OK: - dlg.AddButton(app.Localize(Widget, "OK"), DialogResult.Ok, DefaultButton == MessageBoxDefaultButton.OK); + dlg.AddButton(app.Localize(Widget, "OK"), DialogResult.Ok, defaultButton == MessageBoxDefaultButton.OK, defaultButton == MessageBoxDefaultButton.OK); break; case MessageBoxButtons.OKCancel: - dlg.AddButton(app.Localize(Widget, "OK"), DialogResult.Ok, DefaultButton == MessageBoxDefaultButton.OK); - dlg.AddButton(app.Localize(Widget, "Cancel"), DialogResult.Cancel, DefaultButton == MessageBoxDefaultButton.Cancel, true); + dlg.AddButton(app.Localize(Widget, "OK"), DialogResult.Ok, defaultButton == MessageBoxDefaultButton.OK); + dlg.AddButton(app.Localize(Widget, "Cancel"), DialogResult.Cancel, defaultButton == MessageBoxDefaultButton.Cancel, true); break; case MessageBoxButtons.YesNo: - dlg.AddButton(app.Localize(Widget, "&Yes"), DialogResult.Yes, DefaultButton == MessageBoxDefaultButton.Yes); - dlg.AddButton(app.Localize(Widget, "&No"), DialogResult.No, DefaultButton == MessageBoxDefaultButton.No); + dlg.AddButton(app.Localize(Widget, "&Yes"), DialogResult.Yes, defaultButton == MessageBoxDefaultButton.Yes); + dlg.AddButton(app.Localize(Widget, "&No"), DialogResult.No, defaultButton == MessageBoxDefaultButton.No); break; case MessageBoxButtons.YesNoCancel: - dlg.AddButton(app.Localize(Widget, "Cancel"), DialogResult.Cancel, DefaultButton == MessageBoxDefaultButton.Cancel, true); - dlg.AddButton(app.Localize(Widget, "&No"), DialogResult.No, DefaultButton == MessageBoxDefaultButton.No); - dlg.AddButton(app.Localize(Widget, "&Yes"), DialogResult.Yes, DefaultButton == MessageBoxDefaultButton.Yes); + dlg.AddButton(app.Localize(Widget, "Cancel"), DialogResult.Cancel, defaultButton == MessageBoxDefaultButton.Cancel, true); + dlg.AddButton(app.Localize(Widget, "&No"), DialogResult.No, defaultButton == MessageBoxDefaultButton.No); + dlg.AddButton(app.Localize(Widget, "&Yes"), DialogResult.Yes, defaultButton == MessageBoxDefaultButton.Yes); break; } @@ -54,6 +77,28 @@ public DialogResult ShowDialog(Control parent) return dlg.Result as DialogResult? ?? DialogResult.Cancel; } + + private Image GetImage() + { + SystemIconType icon = SystemIconType.Error; + switch (Type) + { + case MessageBoxType.Information: + icon = SystemIconType.Information; + break; + case MessageBoxType.Warning: + icon = SystemIconType.Warning; + break; + case MessageBoxType.Error: + icon = SystemIconType.Error; + break; + case MessageBoxType.Question: + icon = SystemIconType.Question; + break; + + } + return SystemIcons.Get(icon, SystemIconSize.Large)?.WithSize(32, 32); + } } @@ -64,8 +109,7 @@ public class ThemedMessageBox : Dialog { readonly Label textLabel = new Label(); readonly ImageView image = new ImageView(); - - + /// /// Gets or sets the result of the dialog /// @@ -76,21 +120,60 @@ public class ThemedMessageBox : Dialog /// public ThemedMessageBox() { - Closeable = false; ShowInTaskbar = false; Resizable = false; + Closeable = false; + textLabel.VerticalAlignment = VerticalAlignment.Center; var layout = new DynamicLayout(); layout.Padding = new Padding(22, 28); layout.DefaultSpacing = new Size(8, 8); - layout.AddRow(TableLayout.AutoSized(image, centered: true), textLabel); + layout.AddRow(new TableLayout(image, null), textLabel); Content = layout; + HandleEvent(KeyDownEvent); } + /// + protected override void OnPreLoad(EventArgs e) + { + base.OnPreLoad(e); + CalculateSize(); + } + + private void CalculateSize() + { + const int minWidth = 340; + const float maxRatio = 1000f; + const float idealRatio = 2f; + const int increment = 2; + var screen = Screen ?? Screen.PrimaryScreen; + var workingArea = screen.WorkingArea; + SizeF? lastGoodSize = null; + SizeF available = new SizeF(workingArea.Width * .7f, float.PositiveInfinity); + SizeF size; + do + { + size = textLabel.GetPreferredSize(available); + var ratio = size.Width / size.Height; + if (ratio <= maxRatio && size.Width >= minWidth) + { + lastGoodSize = size; + // we're at an ideal ratio of width to height, let's use it. + if (ratio <= idealRatio) + break; + } + available.Width = (int)Math.Floor(Math.Min(available.Width, size.Width) - increment); + } while (available.Width >= minWidth + increment); + + if (lastGoodSize != null) + textLabel.Size = (Size)lastGoodSize.Value; + } + + /// protected override void OnKeyDown(KeyEventArgs e) { @@ -121,7 +204,10 @@ public void AddButton(string text, object result, bool isDefault = false, bool i Result = result; Close(); }; - PositiveButtons.Add(button); + if (isAbort && !isDefault) + NegativeButtons.Add(button); + else + PositiveButtons.Add(button); if (isDefault) { @@ -142,7 +228,10 @@ public void AddButton(string text, object result, bool isDefault = false, bool i public string Text { get => textLabel.Text; - set => textLabel.Text = value; + set + { + textLabel.Text = value; + } } /// diff --git a/src/Eto/IO/SystemIcons.cs b/src/Eto/IO/SystemIcons.cs index 00ee3fc963..437dca822f 100644 --- a/src/Eto/IO/SystemIcons.cs +++ b/src/Eto/IO/SystemIcons.cs @@ -5,6 +5,7 @@ namespace Eto.IO; /// /// (c) 2014 by Curtis Wensley /// See LICENSE for full terms +[Obsolete("Use Eto.Drawing.SystemIconType instead")] public enum StaticIconType { /// @@ -22,6 +23,7 @@ public enum StaticIconType /// /// (c) 2014 by Curtis Wensley /// See LICENSE for full terms +[Obsolete("Use Eto.Drawing.SystemIconSize instead")] public enum IconSize { /// @@ -39,25 +41,9 @@ public enum IconSize /// /// (c) 2014 by Curtis Wensley /// See LICENSE for full terms -[Handler(typeof(SystemIcons.IHandler))] +[Obsolete("Use Eto.Drawing.SystemImages instead")] public static class SystemIcons { - static IHandler Handler { get { return Platform.Instance.CreateShared(); } } - - static readonly object cacheKey = new object(); - - static Dictionary GetLookupTable(IconSize size) - { - var htSizes = Platform.Instance.Cache>(cacheKey); - Dictionary htIcons; - if (!htSizes.TryGetValue(size, out htIcons)) - { - htIcons = new Dictionary(); - htSizes.Add(size, htIcons); - } - return htIcons; - } - /// /// Gets a file icon for the specified file /// @@ -70,15 +56,7 @@ static Dictionary GetLookupTable(IconSize size) /// Size of the icon. public static Icon GetFileIcon(string fileName, IconSize size) { - var htIcons = GetLookupTable(size); - string ext = Path.GetExtension(fileName).ToUpperInvariant(); - Icon icon; - if (!htIcons.TryGetValue(ext, out icon)) - { - icon = Handler.GetFileIcon(fileName, size); - htIcons.Add(ext, icon); - } - return icon; + return Drawing.SystemIcons.GetFileIcon(fileName, size == IconSize.Large ? Drawing.SystemIconSize.Large : Drawing.SystemIconSize.Small); } /// @@ -89,39 +67,7 @@ public static Icon GetFileIcon(string fileName, IconSize size) /// Size of the icon. public static Icon GetStaticIcon(StaticIconType type, IconSize size) { - var htIcons = GetLookupTable(size); - Icon icon; - if (!htIcons.TryGetValue(type, out icon)) - { - icon = Handler.GetStaticIcon(type, size); - htIcons.Add(type, icon); - } - return icon; - } - - /// - /// Handler interface for the methods - /// - public interface IHandler - { - /// - /// Gets a file icon for the specified file - /// - /// - /// The file does not necessarily have to exist for the icon to be retrieved, though if it is a specific file - /// then the platform may be able to return a file-specific icon if one is available. - /// - /// The icon for the specified file name. - /// Name of the file to get the icon for. - /// Size of the icon. - Icon GetFileIcon(string fileName, IconSize size); - - /// - /// Gets a static system-defined icon for the specified type. - /// - /// The static icon for the specified type. - /// Type of the static icon to retrieve. - /// Size of the icon. - Icon GetStaticIcon(StaticIconType type, IconSize size); + var dtype = type == StaticIconType.OpenDirectory ? Drawing.SystemIconType.OpenDirectory : Drawing.SystemIconType.CloseDirectory; + return Drawing.SystemIcons.Get(dtype, size == IconSize.Large ? Drawing.SystemIconSize.Large : Drawing.SystemIconSize.Small); } } \ No newline at end of file diff --git a/test/Eto.Test/Sections/Drawing/SystemIconsSection.cs b/test/Eto.Test/Sections/Drawing/SystemIconsSection.cs new file mode 100644 index 0000000000..9dfbd22d24 --- /dev/null +++ b/test/Eto.Test/Sections/Drawing/SystemIconsSection.cs @@ -0,0 +1,114 @@ + +namespace Eto.Test.Sections.Drawing; + +[Section("Drawing", typeof(SystemIcons))] +public class SystemIconsSection : Scrollable +{ + Panel _iconPanel; + Panel _fileIconPanel; + SystemIconSize _iconSize; + private string _filePath; + + + IEnumerable<(string name, Icon icon)> GetIcons() + { + foreach (var type in Enum.GetValues(typeof(SystemIconType)).OfType()) + { + yield return (type.ToString(), SystemIcons.Get(type.Value, IconSize)); + } + } + + SystemIconSize IconSize + { + get => _iconSize; + set + { + _iconSize = value; + SetSystemIcons(); + SetFileIcon(); + } + } + + string FilePath + { + get => _filePath; + set + { + _filePath = value; + SetFileIcon(); + } + } + + public SystemIconsSection() + { + _iconPanel = new Panel(); + _fileIconPanel = new Panel(); + + var fileSelect = new FilePicker(); + fileSelect.Bind(c => c.FilePath, this, c => c.FilePath); + + var iconSizeDropDown = new EnumDropDown(); + iconSizeDropDown.SelectedValueBinding.Bind(this, c => c.IconSize); + + var layout = new DynamicLayout { Padding = 10, DefaultSpacing = new Size(5, 5) }; + + layout.BeginCentered(); + layout.AddRow("Size:", iconSizeDropDown); + layout.EndCentered(); + + layout.BeginCentered(); + layout.AddRow("File:", fileSelect); + layout.EndCentered(); + layout.Add(_fileIconPanel); + + layout.Add(_iconPanel, yscale: true); + + SetSystemIcons(); + Content = layout; + } + + private void SetFileIcon() + { + if (FilePath != null && File.Exists(FilePath)) + _fileIconPanel.Content = SystemIcons.GetFileIcon(FilePath, IconSize); + else + _fileIconPanel.Content = null; + } + + private void SetSystemIcons() + { + var layout = new DynamicLayout(); + layout.BeginCentered(spacing: new Size(10, 10), yscale: true); + + layout.BeginVertical(spacing: new Size(5, 5)); + layout.BeginHorizontal(); + int count = 0; + foreach (var type in GetIcons()) + { + var icon = type.icon; + var text = type.name; + var image = new ImageView(); + var label = new Label + { + Text = text, + TextAlignment = TextAlignment.Center + }; + image.Image = icon; + + layout.Add(new TableLayout(image, label)); + + if (count++ > 3) + { + count = 0; + layout.EndBeginHorizontal(); + } + } + layout.EndHorizontal(); + layout.EndVertical(); + + layout.EndCentered(); + + _iconPanel.Content = layout; + } + +} diff --git a/test/Eto.Test/UnitTests/Forms/MessageBoxTests.cs b/test/Eto.Test/UnitTests/Forms/MessageBoxTests.cs index 989a6365ca..b1d08ec79a 100644 --- a/test/Eto.Test/UnitTests/Forms/MessageBoxTests.cs +++ b/test/Eto.Test/UnitTests/Forms/MessageBoxTests.cs @@ -1,43 +1,70 @@ using NUnit.Framework; -namespace Eto.Test.UnitTests.Forms +namespace Eto.Test.UnitTests.Forms; + +[TestFixture] +public class MessageBoxTests : TestBase { - [TestFixture] - public class MessageBoxTests : TestBase + public static IEnumerable GetMessages() + { + yield return Utility.GenerateLoremText(2); + yield return Utility.GenerateLoremText(10); + yield return Utility.GenerateLoremText(20); + yield return Utility.GenerateLoremText(100, true); + yield return Utility.GenerateLoremText(1000, true); + yield return Utility.GenerateLoremText(1000, false); + yield return Utility.GenerateLoremText(3000, true); + yield return Utility.GenerateLoremText(3000, false); + } + + [TestCaseSource(nameof(GetMessages))] + [InvokeOnUI] + [ManualTest] + public void DifferentSizedMessagesShouldWrap(string message) + { + MessageBox.Show(message, MessageBoxType.Question); + } + + [TestCaseSource(nameof(GetMessages))] + [InvokeOnUI] + [ManualTest] + public void MessageBoxShouldCenterParent(string message) + { + MessageBox.Show(Application.Instance.MainForm, message, MessageBoxType.Question); + } + + [Test, ManualTest, InvokeOnUI] + public void OpenFromSecondaryDialogShouldNotChangeItsOrder() { - [Test, ManualTest, InvokeOnUI] - public void OpenFromSecondaryDialogShouldNotChangeItsOrder() + var btn1 = new Button { Text = "Click Me" }; + btn1.Click += (s, e) => { - var btn1 = new Button { Text = "Click Me" }; - btn1.Click += (s, e) => + var btn2 = new Button { Text = "Show MessageBox" }; + btn2.Click += (s1, e1) => { - var btn2 = new Button { Text = "Show MessageBox" }; - btn2.Click += (s1, e1) => - { - MessageBox.Show(btn2.ParentWindow, "This is a message."); - }; - var dlg2 = new Dialog + MessageBox.Show(btn2.ParentWindow, "This is a message."); + }; + var dlg2 = new Dialog + { + Title = "Test MessageBox", + ClientSize = new Size(200, 200), + Content = new TableLayout { - Title = "Test MessageBox", - ClientSize = new Size(200, 200), - Content = new TableLayout - { - Rows = { + Rows = { null, "This dialog should remain on top\nafter the message box closes", TableLayout.AutoSized(btn2, centered: true), null - } } - }; - dlg2.ShowModal(btn1.ParentWindow); + } }; - var dlg1 = new Dialog - { - ClientSize = new Size(200, 200), - Content = TableLayout.AutoSized(btn1, centered: true) - }; - dlg1.ShowModal(Application.Instance.MainForm); - } + dlg2.ShowModal(btn1.ParentWindow); + }; + var dlg1 = new Dialog + { + ClientSize = new Size(200, 200), + Content = TableLayout.AutoSized(btn1, centered: true) + }; + dlg1.ShowModal(Application.Instance.MainForm); } -} +} \ No newline at end of file diff --git a/test/Eto.Test/UnitTests/Forms/ThemedMessageBoxTests.cs b/test/Eto.Test/UnitTests/Forms/ThemedMessageBoxTests.cs new file mode 100755 index 0000000000..b2decf753d --- /dev/null +++ b/test/Eto.Test/UnitTests/Forms/ThemedMessageBoxTests.cs @@ -0,0 +1,33 @@ + +using NUnit.Framework; + +namespace Eto.Test.UnitTests.Forms; + +[TestFixture] +public class ThemedMessageBoxTests : MessageBoxTests, IDisposable +{ + Platform themedPlatform; + IDisposable context; + public ThemedMessageBoxTests() + { + Application.Instance.Invoke(() => + { + var mainForm = Application.Instance.MainForm; + themedPlatform = (Platform)Activator.CreateInstance(Platform.Instance.GetType()); + themedPlatform.Add(() => new Eto.Forms.ThemedControls.ThemedMessageBoxHandler()); + context = themedPlatform.Context; + var app = new Application(themedPlatform).Attach(); + app.MainForm = mainForm; + }); + } + + public void Dispose() + { + Application.Instance.Invoke(() => + { + context?.Dispose(); + context = null; + }); + } +} + diff --git a/test/Eto.Test/Utility.cs b/test/Eto.Test/Utility.cs index aa9fab51fd..a4d13c34d4 100755 --- a/test/Eto.Test/Utility.cs +++ b/test/Eto.Test/Utility.cs @@ -2,10 +2,11 @@ namespace Eto.Test { public static class Utility { + public static string LoremText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; public static string LoremTextWithTwoParagraphs = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; public static string LoremTextWithNewLines = "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum."; - public static string GenerateLoremText(int length) + public static string GenerateLoremText(int length, bool includeParagraphs = true) { var words = LoremTextWithTwoParagraphs.Split(' '); var sb = new StringBuilder(); @@ -15,10 +16,11 @@ public static string GenerateLoremText(int length) if (loremIndex >= words.Length) { loremIndex = 0; - sb.AppendLine(); + if (includeParagraphs) + sb.AppendLine(); } - if (i > 0) - sb.Append(" "); + else if (i > 0) + sb.Append(' '); sb.Append(words[loremIndex++]); } return sb.ToString();